karajan-code 1.43.0 → 1.44.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "1.43.0",
3
+ "version": "1.44.0",
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",
@@ -9,6 +9,7 @@ import { detectAvailableAgents } from "../utils/agent-detect.js";
9
9
  import { createWizard, isTTY } from "../utils/wizard.js";
10
10
  import { runCommand } from "../utils/process.js";
11
11
  import { getInstallCommand } from "../utils/os-detect.js";
12
+ import { detectOsLocale, SUPPORTED_LANGUAGES } from "../utils/locale.js";
12
13
 
13
14
  async function runWizard(config, logger) {
14
15
  const agents = await detectAvailableAgents();
@@ -88,6 +89,31 @@ async function runWizard(config, logger) {
88
89
  config.development.require_test_changes = methodology === "tdd";
89
90
  logger.info(` -> Methodology: ${methodology}`);
90
91
 
92
+ const detectedLocale = detectOsLocale();
93
+ const allLangEntries = Object.entries(SUPPORTED_LANGUAGES).map(([code, name]) => ({
94
+ label: `${name} (${code})`,
95
+ value: code,
96
+ available: true
97
+ }));
98
+ // Put detected locale first so it becomes the default selection
99
+ const languageOptions = [
100
+ ...allLangEntries.filter((o) => o.value === detectedLocale),
101
+ ...allLangEntries.filter((o) => o.value !== detectedLocale)
102
+ ];
103
+
104
+ const pipelineLang = await wizard.select("Pipeline language:", languageOptions);
105
+ config.language = pipelineLang;
106
+ logger.info(` -> Pipeline language: ${pipelineLang}`);
107
+
108
+ // Reorder for HU language with pipeline language as default
109
+ const huLangOptions = [
110
+ ...allLangEntries.filter((o) => o.value === pipelineLang),
111
+ ...allLangEntries.filter((o) => o.value !== pipelineLang)
112
+ ];
113
+ const huLang = await wizard.select("HU language (for user stories):", huLangOptions);
114
+ config.hu_language = huLang;
115
+ logger.info(` -> HU language: ${huLang}`);
116
+
91
117
  logger.info("");
92
118
  } finally {
93
119
  wizard.close();
@@ -290,6 +316,13 @@ export async function initCommand({ logger, flags = {} }) {
290
316
  const { config, exists: configExists } = await loadConfig();
291
317
  const interactive = flags.noInteractive !== true && isTTY();
292
318
 
319
+ // Auto-detect language from OS locale for non-interactive mode
320
+ if (!interactive && !configExists) {
321
+ const detectedLocale = detectOsLocale();
322
+ config.language = detectedLocale;
323
+ config.hu_language = detectedLocale;
324
+ }
325
+
293
326
  await handleConfigSetup({ config, configExists, interactive, configPath, logger });
294
327
  await ensureReviewRules(reviewRulesPath, logger);
295
328
  await ensureCoderRules(coderRulesPath, logger);
package/src/config.js CHANGED
@@ -123,6 +123,8 @@ const DEFAULTS = {
123
123
  port: 4000,
124
124
  auto_start: false
125
125
  },
126
+ language: "en",
127
+ hu_language: "en",
126
128
  policies: {},
127
129
  serena: { enabled: false },
128
130
  planning_game: { enabled: false, project_id: null, codeveloper: null },
@@ -1,4 +1,5 @@
1
1
  import { loadAvailableSkills, buildSkillSection } from "../skills/skill-loader.js";
2
+ import { getLanguageInstruction } from "../utils/locale.js";
2
3
 
3
4
  const SUBAGENT_PREAMBLE = [
4
5
  "IMPORTANT: You are running as a Karajan sub-agent.",
@@ -8,8 +9,9 @@ const SUBAGENT_PREAMBLE = [
8
9
 
9
10
  export const VALID_VERDICTS = new Set(["ready", "needs_clarification"]);
10
11
 
11
- export async function buildArchitectPrompt({ task, instructions, researchContext = null, productContext = null, projectDir = null }) {
12
- const sections = [SUBAGENT_PREAMBLE];
12
+ export async function buildArchitectPrompt({ task, instructions, researchContext = null, productContext = null, projectDir = null, language = "en" }) {
13
+ const langInstruction = getLanguageInstruction(language);
14
+ const sections = [SUBAGENT_PREAMBLE, ...(langInstruction ? [langInstruction] : [])];
13
15
 
14
16
  if (instructions) {
15
17
  sections.push(instructions);
@@ -1,5 +1,6 @@
1
1
  import { RTK_INSTRUCTIONS } from "./rtk-snippet.js";
2
2
  import { loadAvailableSkills, buildSkillSection } from "../skills/skill-loader.js";
3
+ import { getLanguageInstruction } from "../utils/locale.js";
3
4
 
4
5
  const SUBAGENT_PREAMBLE = [
5
6
  "IMPORTANT: You are running as a Karajan sub-agent.",
@@ -32,9 +33,11 @@ const SERENA_INSTRUCTIONS = [
32
33
  "Fall back to reading files only when Serena tools are not sufficient."
33
34
  ].join("\n");
34
35
 
35
- export async function buildCoderPrompt({ task, reviewerFeedback = null, sonarSummary = null, coderRules = null, methodology = "tdd", serenaEnabled = false, rtkAvailable = false, deferredContext = null, productContext = null, plan = null, projectDir = null }) {
36
+ export async function buildCoderPrompt({ task, reviewerFeedback = null, sonarSummary = null, coderRules = null, methodology = "tdd", serenaEnabled = false, rtkAvailable = false, deferredContext = null, productContext = null, plan = null, projectDir = null, language = "en" }) {
37
+ const langInstruction = getLanguageInstruction(language);
36
38
  const sections = [
37
39
  serenaEnabled ? SUBAGENT_PREAMBLE_SERENA : SUBAGENT_PREAMBLE,
40
+ ...(langInstruction ? [langInstruction] : []),
38
41
  `Task:\n${task}`,
39
42
  "Implement directly in the repository.",
40
43
  "Keep changes minimal and production-ready.",
@@ -1,4 +1,5 @@
1
1
  import { extractFirstJson } from "../utils/json-extract.js";
2
+ import { getLanguageInstruction } from "../utils/locale.js";
2
3
 
3
4
  const SUBAGENT_PREAMBLE = [
4
5
  "IMPORTANT: You are running as a Karajan sub-agent.",
@@ -21,8 +22,9 @@ const DIMENSION_KEYS = [
21
22
  * @param {{stories: Array<{id: string, text: string}>, instructions: string|null, context?: string|null}} params
22
23
  * @returns {string} The assembled prompt.
23
24
  */
24
- export function buildHuReviewerPrompt({ stories, instructions, context = null, productContext = null }) {
25
- const sections = [SUBAGENT_PREAMBLE];
25
+ export function buildHuReviewerPrompt({ stories, instructions, context = null, productContext = null, hu_language = "en" }) {
26
+ const langInstruction = getLanguageInstruction(hu_language);
27
+ const sections = [SUBAGENT_PREAMBLE, ...(langInstruction ? [langInstruction] : [])];
26
28
 
27
29
  if (instructions) {
28
30
  sections.push(instructions);
@@ -1,5 +1,6 @@
1
1
  import { RTK_INSTRUCTIONS } from "./rtk-snippet.js";
2
2
  import { loadAvailableSkills, buildSkillSection } from "../skills/skill-loader.js";
3
+ import { getLanguageInstruction } from "../utils/locale.js";
3
4
 
4
5
  const SUBAGENT_PREAMBLE = [
5
6
  "IMPORTANT: You are running as a Karajan sub-agent.",
@@ -23,11 +24,13 @@ const SERENA_INSTRUCTIONS = [
23
24
  "Fall back to reading files only when Serena tools are not sufficient."
24
25
  ].join("\n");
25
26
 
26
- export async function buildReviewerPrompt({ task, diff, reviewRules, mode, serenaEnabled = false, rtkAvailable = false, productContext = null, projectDir = null }) {
27
+ export async function buildReviewerPrompt({ task, diff, reviewRules, mode, serenaEnabled = false, rtkAvailable = false, productContext = null, projectDir = null, language = "en" }) {
27
28
  const truncatedDiff = diff.length > 12000 ? `${diff.slice(0, 12000)}\n\n[TRUNCATED]` : diff;
28
29
 
30
+ const langInstruction = getLanguageInstruction(language);
29
31
  const sections = [
30
32
  serenaEnabled ? SUBAGENT_PREAMBLE_SERENA : SUBAGENT_PREAMBLE,
33
+ ...(langInstruction ? [langInstruction] : []),
31
34
  `You are a code reviewer in ${mode} mode.`,
32
35
  "CRITICAL SCOPE RULE: Only review changes that are part of the diff below. Do NOT flag issues in unchanged code, missing features planned for future tasks, or improvements outside the scope of this task. If the diff is correct for what the task asks, approve it — even if the broader codebase has other issues.",
33
36
  "Only block approval for issues IN THE DIFF that are bugs, security vulnerabilities, or clear violations of the review rules.",
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
+ // TODO: i18n display messages
5
6
  const DISPLAY_PKG_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../package.json");
6
7
  const DISPLAY_VERSION = JSON.parse(readFileSync(DISPLAY_PKG_PATH, "utf8")).version;
7
8
 
@@ -0,0 +1,59 @@
1
+ /**
2
+ * OS locale detection and language instruction generation for i18n support.
3
+ */
4
+
5
+ export const SUPPORTED_LANGUAGES = {
6
+ en: "English",
7
+ es: "Español"
8
+ };
9
+
10
+ const LANG_ENV_VARS = ["LANG", "LANGUAGE", "LC_ALL", "LC_MESSAGES"];
11
+
12
+ /**
13
+ * Detect the 2-letter language code from OS environment variables.
14
+ * Checks LANG, LANGUAGE, LC_ALL, LC_MESSAGES in order.
15
+ * @returns {string} 2-letter language code (e.g. "en", "es")
16
+ */
17
+ export function detectOsLocale() {
18
+ for (const envVar of LANG_ENV_VARS) {
19
+ const value = process.env[envVar];
20
+ if (value) {
21
+ const code = extractLangCode(value);
22
+ if (code) return code;
23
+ }
24
+ }
25
+ return "en";
26
+ }
27
+
28
+ /**
29
+ * Extract a 2-letter language code from a locale string.
30
+ * Handles formats like "es_ES.UTF-8", "en_US", "es", "C.UTF-8".
31
+ * @param {string} locale
32
+ * @returns {string|null}
33
+ */
34
+ function extractLangCode(locale) {
35
+ const trimmed = locale.trim();
36
+ if (!trimmed || trimmed === "C" || trimmed === "POSIX") return null;
37
+ // Match first 2 lowercase letters before _, ., or end
38
+ const match = /^([a-z]{2})(?:[_.\-]|$)/i.exec(trimmed);
39
+ if (match) return match[1].toLowerCase();
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * Get a prompt instruction string for the given language.
45
+ * Returns empty string for English (agents default to English).
46
+ * @param {string} lang - 2-letter language code
47
+ * @returns {string}
48
+ */
49
+ export function getLanguageInstruction(lang) {
50
+ if (!lang || lang === "en") return "";
51
+ const languageName = SUPPORTED_LANGUAGES[lang];
52
+ if (lang === "es") {
53
+ return "IMPORTANT: Respond in Spanish. All output, explanations, and feedback must be in Spanish.";
54
+ }
55
+ if (languageName) {
56
+ return `IMPORTANT: Respond in ${languageName}. All output must be in ${languageName}.`;
57
+ }
58
+ return `IMPORTANT: Respond in ${lang}. All output must be in ${lang}.`;
59
+ }