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 +1 -0
- package/package.json +1 -1
- package/src/commands/doctor.js +31 -0
- package/src/commands/init.js +16 -0
- package/src/mcp/run-kj.js +5 -2
- package/src/mcp/server-handlers.js +32 -15
- package/src/mcp/tools.js +9 -0
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
package/src/commands/doctor.js
CHANGED
|
@@ -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
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
|
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
|
|
29
|
-
*
|
|
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
|
-
|
|
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
|
}
|