karajan-code 1.9.3 → 1.9.5

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
@@ -420,6 +420,18 @@ After `npm install -g karajan-code`, the MCP server is auto-registered in Claude
420
420
  | `kj_review` | Run reviewer-only mode |
421
421
  | `kj_plan` | Generate implementation plan with heartbeat/stall telemetry and clearer diagnostics |
422
422
 
423
+ ### MCP restart after version updates
424
+
425
+ If you update Karajan Code (for example `npm install -g karajan-code` to a new version) while your MCP host session is still open, the current `karajan-mcp` process may exit and the host can show `Transport closed`.
426
+
427
+ This is expected behavior: the MCP server detects a version mismatch and exits so the host can spawn a fresh process with the new code.
428
+
429
+ Quick recovery:
430
+
431
+ 1. Restart your MCP host session (Claude/Codex/new terminal session).
432
+ 2. Verify the server is listed (`codex mcp list` or your host equivalent).
433
+ 3. Run a lightweight check (`kj_config`) before continuing with larger runs.
434
+
423
435
  ### Recommended Companion MCPs
424
436
 
425
437
  Karajan Code works great on its own, but combining it with these MCP servers gives your AI agent a complete development environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "1.9.3",
3
+ "version": "1.9.5",
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",
@@ -70,6 +70,19 @@ function createStreamJsonFilter(onOutput) {
70
70
  };
71
71
  }
72
72
 
73
+ /**
74
+ * Build a clean environment for Claude subprocess.
75
+ * Claude Code 2.x sets CLAUDECODE=1 to detect nesting. When Karajan's MCP
76
+ * server runs inside Claude Code and spawns `claude -p`, the child inherits
77
+ * this variable and refuses to start. Stripping it allows the subprocess to
78
+ * run normally — it is a separate, non-interactive invocation, not a true
79
+ * nested session.
80
+ */
81
+ function cleanEnv() {
82
+ const { CLAUDECODE, ...rest } = process.env;
83
+ return rest;
84
+ }
85
+
73
86
  export class ClaudeAgent extends BaseAgent {
74
87
  async runTask(task) {
75
88
  const role = task.role || "coder";
@@ -77,11 +90,14 @@ export class ClaudeAgent extends BaseAgent {
77
90
  const model = this.getRoleModel(role);
78
91
  if (model) args.push("--model", model);
79
92
 
93
+ const env = cleanEnv();
94
+
80
95
  // Use stream-json when onOutput is provided to get real-time feedback
81
96
  if (task.onOutput) {
82
97
  args.push("--output-format", "stream-json");
83
98
  const streamFilter = createStreamJsonFilter(task.onOutput);
84
99
  const res = await runCommand(resolveBin("claude"), args, {
100
+ env,
85
101
  onOutput: streamFilter,
86
102
  silenceTimeoutMs: task.silenceTimeoutMs,
87
103
  timeout: task.timeoutMs
@@ -90,7 +106,7 @@ export class ClaudeAgent extends BaseAgent {
90
106
  return { ok: res.exitCode === 0, output, error: res.stderr, exitCode: res.exitCode };
91
107
  }
92
108
 
93
- const res = await runCommand(resolveBin("claude"), args);
109
+ const res = await runCommand(resolveBin("claude"), args, { env });
94
110
  return { ok: res.exitCode === 0, output: res.stdout, error: res.stderr, exitCode: res.exitCode };
95
111
  }
96
112
 
@@ -99,6 +115,7 @@ export class ClaudeAgent extends BaseAgent {
99
115
  const model = this.getRoleModel(task.role || "reviewer");
100
116
  if (model) args.push("--model", model);
101
117
  const res = await runCommand(resolveBin("claude"), args, {
118
+ env: cleanEnv(),
102
119
  onOutput: task.onOutput,
103
120
  silenceTimeoutMs: task.silenceTimeoutMs,
104
121
  timeout: task.timeoutMs
@@ -21,6 +21,7 @@ import { parseMaybeJsonString } from "../review/parser.js";
21
21
  import { computeBaseRef, generateDiff } from "../review/diff-generator.js";
22
22
  import { resolveReviewProfile } from "../review/profiles.js";
23
23
  import { createRunLog, readRunLog } from "../utils/run-log.js";
24
+ import { currentBranch } from "../utils/git.js";
24
25
 
25
26
  /**
26
27
  * Resolve the user's project directory via MCP roots.
@@ -109,6 +110,13 @@ export function classifyError(error) {
109
110
  };
110
111
  }
111
112
 
113
+ if (lower.includes("you are on the base branch")) {
114
+ return {
115
+ category: "branch_error",
116
+ suggestion: "Create a feature branch before running Karajan. Use 'git checkout -b feat/<task-description>' and then retry. Do NOT run kj_code directly on the base branch."
117
+ };
118
+ }
119
+
112
120
  if (lower.includes("not a git repository")) {
113
121
  return {
114
122
  category: "git_error",
@@ -119,6 +127,23 @@ export function classifyError(error) {
119
127
  return { category: "unknown", suggestion: null };
120
128
  }
121
129
 
130
+ export async function assertNotOnBaseBranch(config) {
131
+ const baseBranch = config?.base_branch || "main";
132
+ let branch;
133
+ try {
134
+ branch = await currentBranch();
135
+ } catch {
136
+ return; // not a git repo or detached HEAD — let downstream handle it
137
+ }
138
+ if (branch === baseBranch) {
139
+ throw new Error(
140
+ `You are on the base branch '${baseBranch}'. Karajan needs a feature branch to compute the diff for review. ` +
141
+ `Create a new branch first (e.g. 'git checkout -b feat/<task-description>') and then run this command again. ` +
142
+ `Do NOT run kj_code directly — create the branch first so the full pipeline (code + review) works correctly.`
143
+ );
144
+ }
145
+ }
146
+
122
147
  export function enrichedFailPayload(error, toolName) {
123
148
  const msg = error?.message || String(error);
124
149
  const { category, suggestion } = classifyError(error);
@@ -161,6 +186,7 @@ export function buildAskQuestion(server) {
161
186
 
162
187
  export async function handleRunDirect(a, server, extra) {
163
188
  const config = await buildConfig(a);
189
+ await assertNotOnBaseBranch(config);
164
190
  const logger = createLogger(config.output.log_level, "mcp");
165
191
 
166
192
  const requiredProviders = [
@@ -304,6 +330,7 @@ export async function handlePlanDirect(a, server, extra) {
304
330
 
305
331
  export async function handleCodeDirect(a, server, extra) {
306
332
  const config = await buildConfig(a, "code");
333
+ await assertNotOnBaseBranch(config);
307
334
  const logger = createLogger(config.output.log_level, "mcp");
308
335
 
309
336
  const coderRole = resolveRole(config, "coder");
@@ -352,6 +379,7 @@ export async function handleCodeDirect(a, server, extra) {
352
379
 
353
380
  export async function handleReviewDirect(a, server, extra) {
354
381
  const config = await buildConfig(a, "review");
382
+ await assertNotOnBaseBranch(config);
355
383
  const logger = createLogger(config.output.log_level, "mcp");
356
384
 
357
385
  const reviewerRole = resolveRole(config, "reviewer");