karajan-code 1.26.0 → 1.27.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
@@ -459,6 +459,7 @@ Karajan Code works great on its own, but combining it with these MCP servers giv
459
459
  | [**GitHub MCP**](https://github.com/modelcontextprotocol/servers/tree/main/src/github) | Create PRs, manage issues, read repos directly from the agent | Combine with `--auto-push` for end-to-end: code → review → push → PR |
460
460
  | [**Serena**](https://github.com/oramasearch/serena) | Symbol-level code navigation (find references, go-to-definition) for JS/TS projects | Enable with `--enable-serena` to inject symbol context into coder/reviewer prompts |
461
461
  | [**Chrome DevTools MCP**](https://github.com/anthropics/anthropic-quickstarts/tree/main/chrome-devtools-mcp) | Browser automation, screenshots, console/network inspection | Verify UI changes visually after `kj` modifies frontend code |
462
+ | [**RTK**](https://github.com/rtk-ai/rtk) | Reduces LLM token consumption by 60-90% on Bash command outputs (git, test, build) | Install globally with `brew install rtk && rtk init --global` — all KJ agent commands automatically compressed |
462
463
 
463
464
  ## Role Templates
464
465
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "1.26.0",
3
+ "version": "1.27.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",
@@ -204,6 +204,36 @@ async function checkBecariaInfra(config) {
204
204
  return checks;
205
205
  }
206
206
 
207
+ async function checkRtk() {
208
+ try {
209
+ const res = await runCommand("rtk", ["--version"]);
210
+ if (res.exitCode === 0) {
211
+ return {
212
+ name: "rtk",
213
+ label: "RTK (Rust Token Killer)",
214
+ ok: true,
215
+ detail: `${res.stdout.trim()} — token savings active`,
216
+ fix: null
217
+ };
218
+ }
219
+ return {
220
+ name: "rtk",
221
+ label: "RTK (Rust Token Killer)",
222
+ ok: true,
223
+ detail: "Not found — install for 60-90% token savings: brew install rtk",
224
+ fix: null
225
+ };
226
+ } catch {
227
+ return {
228
+ name: "rtk",
229
+ label: "RTK (Rust Token Killer)",
230
+ ok: true,
231
+ detail: "Not found — install for 60-90% token savings: brew install rtk",
232
+ fix: null
233
+ };
234
+ }
235
+ }
236
+
207
237
  async function checkRuleFiles(config) {
208
238
  const projectDir = config.projectDir || process.cwd();
209
239
  const reviewRules = await loadFirstExisting(resolveRoleMdPath("reviewer", projectDir));
@@ -248,6 +278,7 @@ export async function runChecks({ config }) {
248
278
  }
249
279
 
250
280
  checks.push(...await checkRuleFiles(config));
281
+ checks.push(await checkRtk());
251
282
 
252
283
  return checks;
253
284
  }
@@ -7,6 +7,7 @@ import { exists, ensureDir } from "../utils/fs.js";
7
7
  import { getKarajanHome } from "../utils/paths.js";
8
8
  import { detectAvailableAgents } from "../utils/agent-detect.js";
9
9
  import { createWizard, isTTY } from "../utils/wizard.js";
10
+ import { runCommand } from "../utils/process.js";
10
11
 
11
12
  async function runWizard(config, logger) {
12
13
  const agents = await detectAvailableAgents();
@@ -270,6 +271,21 @@ export async function initCommand({ logger, flags = {} }) {
270
271
  await ensureReviewRules(reviewRulesPath, logger);
271
272
  await ensureCoderRules(coderRulesPath, logger);
272
273
  await installSkills(logger, interactive);
274
+
275
+ // Check RTK availability and inform user
276
+ let hasRtk = false;
277
+ try {
278
+ const rtkRes = await runCommand("rtk", ["--version"]);
279
+ hasRtk = rtkRes.exitCode === 0;
280
+ } catch {
281
+ hasRtk = false;
282
+ }
283
+ if (!hasRtk) {
284
+ logger.info("");
285
+ logger.info("RTK (Rust Token Killer) can reduce token usage by 60-90%.");
286
+ logger.info(" Install: brew install rtk && rtk init --global");
287
+ }
288
+
273
289
  await setupSonarQube(config, logger);
274
290
  await scaffoldBecariaGateway(config, flags, logger);
275
291
  }
package/src/mcp/run-kj.js CHANGED
@@ -76,11 +76,14 @@ export async function runKjCommand({ command, commandArgs = [], options = {}, en
76
76
 
77
77
  const timeout = options.timeoutMs ? Number(options.timeoutMs) : undefined;
78
78
 
79
- const result = await execa("node", args, {
79
+ const execOpts = {
80
80
  env: runEnv,
81
81
  reject: false,
82
82
  timeout
83
- });
83
+ };
84
+ if (options.projectDir) execOpts.cwd = options.projectDir;
85
+
86
+ const result = await execa("node", args, execOpts);
84
87
 
85
88
  const ok = result.exitCode === 0;
86
89
  const payload = {
@@ -25,20 +25,37 @@ import { currentBranch } from "../utils/git.js";
25
25
  import { isPreflightAcked, ackPreflight, getSessionOverrides } from "./preflight.js";
26
26
 
27
27
  /**
28
- * Resolve the user's project directory via MCP roots.
29
- * Falls back to process.cwd() if roots are not available.
28
+ * Resolve the user's project directory.
29
+ * Priority: 1) explicit projectDir param, 2) MCP roots, 3) error with instructions.
30
30
  */
31
- async function resolveProjectDir(server) {
31
+ async function resolveProjectDir(server, explicitProjectDir) {
32
+ // 1. Explicit projectDir from tool parameter — always wins
33
+ if (explicitProjectDir) return explicitProjectDir;
34
+
35
+ // 2. MCP roots (host-provided workspace directory)
32
36
  try {
33
37
  const { roots } = await server.listRoots();
34
38
  if (roots?.length > 0) {
35
39
  const uri = roots[0].uri;
36
- // MCP roots use file:// URIs
37
40
  if (uri.startsWith("file://")) return new URL(uri).pathname;
38
41
  return uri;
39
42
  }
40
43
  } catch { /* client may not support roots */ }
41
- return process.cwd();
44
+
45
+ // 3. Check if process.cwd() looks like a real project (has package.json or .git)
46
+ const cwd = process.cwd();
47
+ try {
48
+ const hasGit = await fs.access(`${cwd}/.git`).then(() => true).catch(() => false);
49
+ const hasPkg = await fs.access(`${cwd}/package.json`).then(() => true).catch(() => false);
50
+ if (hasGit || hasPkg) return cwd;
51
+ } catch { /* ignore */ }
52
+
53
+ // 4. No valid project directory — fail with clear instructions
54
+ throw new Error(
55
+ `Cannot determine project directory. The MCP server is running from "${cwd}" which does not appear to be your project. ` +
56
+ `Fix: pass the "projectDir" parameter with the absolute path to your project (e.g., projectDir: "/home/user/my-project"), ` +
57
+ `or run "kj init" inside your project directory.`
58
+ );
42
59
  }
43
60
 
44
61
  export function asObject(value) {
@@ -260,7 +277,7 @@ export async function handleRunDirect(a, server, extra) {
260
277
  if (config.pipeline?.security?.enabled) requiredProviders.push(resolveRole(config, "security").provider);
261
278
  await assertAgentsAvailable(requiredProviders);
262
279
 
263
- const projectDir = await resolveProjectDir(server);
280
+ const projectDir = await resolveProjectDir(server, a.projectDir);
264
281
  const runLog = createRunLog(projectDir);
265
282
  runLog.logText(`[kj_run] started — task="${a.task.slice(0, 80)}..."`);
266
283
 
@@ -293,7 +310,7 @@ export async function handleResumeDirect(a, server, extra) {
293
310
  const config = await buildConfig(a);
294
311
  const logger = createLogger(config.output.log_level, "mcp");
295
312
 
296
- const projectDir = await resolveProjectDir(server);
313
+ const projectDir = await resolveProjectDir(server, a.projectDir);
297
314
  const runLog = createRunLog(projectDir);
298
315
  runLog.logText(`[kj_resume] started — session="${a.sessionId}"`);
299
316
 
@@ -349,7 +366,7 @@ export async function handlePlanDirect(a, server, extra) {
349
366
  const plannerRole = resolveRole(config, "planner");
350
367
  await assertAgentsAvailable([plannerRole.provider]);
351
368
 
352
- const projectDir = await resolveProjectDir(server);
369
+ const projectDir = await resolveProjectDir(server, a.projectDir);
353
370
  const runLog = createRunLog(projectDir);
354
371
  const silenceTimeoutMs = Number(config?.session?.max_agent_silence_minutes) > 0
355
372
  ? Math.round(Number(config.session.max_agent_silence_minutes) * 60 * 1000)
@@ -416,7 +433,7 @@ export async function handleCodeDirect(a, server, extra) {
416
433
  const coderRole = resolveRole(config, "coder");
417
434
  await assertAgentsAvailable([coderRole.provider]);
418
435
 
419
- const projectDir = await resolveProjectDir(server);
436
+ const projectDir = await resolveProjectDir(server, a.projectDir);
420
437
  const runLog = createRunLog(projectDir);
421
438
  runLog.logText(`[kj_code] started — provider=${coderRole.provider}`);
422
439
  const emitter = buildDirectEmitter(server, runLog, extra);
@@ -465,7 +482,7 @@ export async function handleReviewDirect(a, server, extra) {
465
482
  const reviewerRole = resolveRole(config, "reviewer");
466
483
  await assertAgentsAvailable([reviewerRole.provider, config.reviewer_options?.fallback_reviewer]);
467
484
 
468
- const projectDir = await resolveProjectDir(server);
485
+ const projectDir = await resolveProjectDir(server, a.projectDir);
469
486
  const runLog = createRunLog(projectDir);
470
487
  runLog.logText(`[kj_review] started — provider=${reviewerRole.provider}`);
471
488
  const emitter = buildDirectEmitter(server, runLog, extra);
@@ -512,7 +529,7 @@ export async function handleDiscoverDirect(a, server, extra) {
512
529
  const discoverRole = resolveRole(config, "discover");
513
530
  await assertAgentsAvailable([discoverRole.provider]);
514
531
 
515
- const projectDir = await resolveProjectDir(server);
532
+ const projectDir = await resolveProjectDir(server, a.projectDir);
516
533
  const runLog = createRunLog(projectDir);
517
534
  runLog.logText(`[kj_discover] started — mode=${a.mode || "gaps"}`);
518
535
  const emitter = buildDirectEmitter(server, runLog, extra);
@@ -565,7 +582,7 @@ export async function handleTriageDirect(a, server, extra) {
565
582
  const triageRole = resolveRole(config, "triage");
566
583
  await assertAgentsAvailable([triageRole.provider]);
567
584
 
568
- const projectDir = await resolveProjectDir(server);
585
+ const projectDir = await resolveProjectDir(server, a.projectDir);
569
586
  const runLog = createRunLog(projectDir);
570
587
  runLog.logText(`[kj_triage] started`);
571
588
  const emitter = buildDirectEmitter(server, runLog, extra);
@@ -609,7 +626,7 @@ export async function handleResearcherDirect(a, server, extra) {
609
626
  const researcherRole = resolveRole(config, "researcher");
610
627
  await assertAgentsAvailable([researcherRole.provider]);
611
628
 
612
- const projectDir = await resolveProjectDir(server);
629
+ const projectDir = await resolveProjectDir(server, a.projectDir);
613
630
  const runLog = createRunLog(projectDir);
614
631
  runLog.logText(`[kj_researcher] started`);
615
632
  const emitter = buildDirectEmitter(server, runLog, extra);
@@ -653,7 +670,7 @@ export async function handleArchitectDirect(a, server, extra) {
653
670
  const architectRole = resolveRole(config, "architect");
654
671
  await assertAgentsAvailable([architectRole.provider]);
655
672
 
656
- const projectDir = await resolveProjectDir(server);
673
+ const projectDir = await resolveProjectDir(server, a.projectDir);
657
674
  const runLog = createRunLog(projectDir);
658
675
  runLog.logText(`[kj_architect] started`);
659
676
  const emitter = buildDirectEmitter(server, runLog, extra);
@@ -780,7 +797,7 @@ function buildReportArgs(a) {
780
797
 
781
798
  async function handleStatus(a, server) {
782
799
  const maxLines = a.lines || 50;
783
- const projectDir = await resolveProjectDir(server);
800
+ const projectDir = await resolveProjectDir(server, a.projectDir);
784
801
  return readRunLog(projectDir, maxLines);
785
802
  }
786
803
 
package/src/mcp/tools.js CHANGED
@@ -53,6 +53,7 @@ export const tools = [
53
53
  required: ["task"],
54
54
  properties: {
55
55
  task: { type: "string", description: "Task description for the coder (can include a Planning Game card ID like KJC-TSK-0042)" },
56
+ projectDir: { type: "string", description: "Absolute path to the project directory. Required when KJ MCP server runs from a different directory than the target project." },
56
57
  pgTask: { type: "string", description: "Planning Game card ID (e.g., KJC-TSK-0042). If provided, fetches full card details as task context and updates card status on completion." },
57
58
  pgProject: { type: "string", description: "Planning Game project ID (e.g., 'Karajan Code'). Required when pgTask is used." },
58
59
  planner: { type: "string" },
@@ -112,6 +113,7 @@ export const tools = [
112
113
  properties: {
113
114
  sessionId: { type: "string", description: "Session ID to resume" },
114
115
  answer: { type: "string", description: "Answer to the question that caused the pause" },
116
+ projectDir: { type: "string", description: "Absolute path to the project directory" },
115
117
  kjHome: { type: "string" }
116
118
  }
117
119
  }
@@ -185,6 +187,7 @@ export const tools = [
185
187
  task: { type: "string" },
186
188
  coder: { type: "string" },
187
189
  coderModel: { type: "string" },
190
+ projectDir: { type: "string", description: "Absolute path to the project directory" },
188
191
  kjHome: { type: "string" }
189
192
  }
190
193
  }
@@ -200,6 +203,7 @@ export const tools = [
200
203
  reviewer: { type: "string" },
201
204
  reviewerModel: { type: "string" },
202
205
  baseRef: { type: "string" },
206
+ projectDir: { type: "string", description: "Absolute path to the project directory" },
203
207
  kjHome: { type: "string" }
204
208
  }
205
209
  }
@@ -226,6 +230,7 @@ export const tools = [
226
230
  plannerModel: { type: "string" },
227
231
  coder: { type: "string", description: "Legacy alias for planner" },
228
232
  coderModel: { type: "string", description: "Legacy alias for plannerModel" },
233
+ projectDir: { type: "string", description: "Absolute path to the project directory" },
229
234
  kjHome: { type: "string" }
230
235
  }
231
236
  }
@@ -242,6 +247,7 @@ export const tools = [
242
247
  context: { type: "string", description: "Additional context for the analysis (e.g., research output)" },
243
248
  pgTask: { type: "string", description: "Planning Game card ID (e.g., KJC-TSK-0042). If provided, fetches full card details as additional context." },
244
249
  pgProject: { type: "string", description: "Planning Game project ID. Required when pgTask is used." },
250
+ projectDir: { type: "string", description: "Absolute path to the project directory" },
245
251
  kjHome: { type: "string" }
246
252
  }
247
253
  }
@@ -254,6 +260,7 @@ export const tools = [
254
260
  required: ["task"],
255
261
  properties: {
256
262
  task: { type: "string", description: "Task description to classify" },
263
+ projectDir: { type: "string", description: "Absolute path to the project directory" },
257
264
  kjHome: { type: "string" }
258
265
  }
259
266
  }
@@ -266,6 +273,7 @@ export const tools = [
266
273
  required: ["task"],
267
274
  properties: {
268
275
  task: { type: "string", description: "Task description to research" },
276
+ projectDir: { type: "string", description: "Absolute path to the project directory" },
269
277
  kjHome: { type: "string" }
270
278
  }
271
279
  }
@@ -279,6 +287,7 @@ export const tools = [
279
287
  properties: {
280
288
  task: { type: "string", description: "Task description to architect" },
281
289
  context: { type: "string", description: "Additional context (e.g., researcher output)" },
290
+ projectDir: { type: "string", description: "Absolute path to the project directory" },
282
291
  kjHome: { type: "string" }
283
292
  }
284
293
  }