karajan-code 1.13.1 → 1.14.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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../src/mcp/server.js";
package/bin/kj.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../src/cli.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "1.13.1",
3
+ "version": "1.14.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",
@@ -37,8 +37,8 @@
37
37
  "README.md"
38
38
  ],
39
39
  "bin": {
40
- "kj": "./src/cli.js",
41
- "karajan-mcp": "./src/mcp/server.js"
40
+ "kj": "bin/kj.js",
41
+ "karajan-mcp": "bin/karajan-mcp.js"
42
42
  },
43
43
  "scripts": {
44
44
  "postinstall": "node scripts/postinstall.js",
package/src/config.js CHANGED
@@ -97,6 +97,7 @@ const DEFAULTS = {
97
97
  disabled_rules: ["javascript:S1116", "javascript:S3776"]
98
98
  }
99
99
  },
100
+ policies: {},
100
101
  serena: { enabled: false },
101
102
  planning_game: { enabled: false, project_id: null, codeveloper: null },
102
103
  becaria: { enabled: false, review_event: "becaria-review", comment_event: "becaria-comment", comment_prefix: true },
@@ -285,6 +286,7 @@ export function applyRunOverrides(config, flags) {
285
286
  out.development.methodology = methodology;
286
287
  out.development.require_test_changes = methodology === "tdd";
287
288
  }
289
+ if (flags.taskType) out.taskType = String(flags.taskType);
288
290
  if (flags.noSonar || flags.sonar === false) out.sonarqube.enabled = false;
289
291
  out.serena = out.serena || { enabled: false };
290
292
  if (flags.enableSerena !== undefined) out.serena.enabled = Boolean(flags.enableSerena);
@@ -0,0 +1,37 @@
1
+ export const VALID_TASK_TYPES = ["sw", "infra", "doc", "add-tests", "refactor"];
2
+
3
+ export const DEFAULT_POLICIES = {
4
+ sw: { tdd: true, sonar: true, reviewer: true, testsRequired: true },
5
+ infra: { tdd: false, sonar: false, reviewer: true, testsRequired: false },
6
+ doc: { tdd: false, sonar: false, reviewer: true, testsRequired: false },
7
+ "add-tests": { tdd: false, sonar: true, reviewer: true, testsRequired: true },
8
+ refactor: { tdd: true, sonar: true, reviewer: true, testsRequired: false },
9
+ };
10
+
11
+ const FALLBACK_TYPE = "sw";
12
+
13
+ /**
14
+ * Resolve pipeline policies for a given taskType.
15
+ * Unknown / null / undefined taskType falls back to "sw" (conservative).
16
+ * configOverrides optionally merges over defaults per taskType.
17
+ */
18
+ export function resolvePolicies(taskType, configOverrides) {
19
+ const resolvedType = VALID_TASK_TYPES.includes(taskType) ? taskType : FALLBACK_TYPE;
20
+ const base = { ...DEFAULT_POLICIES[resolvedType] };
21
+ const overrides = configOverrides?.[resolvedType];
22
+ if (overrides && typeof overrides === "object") {
23
+ Object.assign(base, overrides);
24
+ }
25
+ return base;
26
+ }
27
+
28
+ /**
29
+ * Resolve policies for a taskType and return a flat object with the resolved
30
+ * taskType plus all policy flags. This is the main entry point for the
31
+ * orchestrator to determine which pipeline stages to enable/disable.
32
+ */
33
+ export function applyPolicies({ taskType, policies } = {}) {
34
+ const resolvedType = VALID_TASK_TYPES.includes(taskType) ? taskType : FALLBACK_TYPE;
35
+ const resolved = resolvePolicies(taskType, policies);
36
+ return { taskType: resolvedType, ...resolved };
37
+ }
@@ -3,7 +3,7 @@ import { CoderRole } from "../roles/coder-role.js";
3
3
  import { RefactorerRole } from "../roles/refactorer-role.js";
4
4
  import { SonarRole } from "../roles/sonar-role.js";
5
5
  import { addCheckpoint, markSessionStatus, saveSession, pauseSession } from "../session-store.js";
6
- import { generateDiff } from "../review/diff-generator.js";
6
+ import { generateDiff, getUntrackedFiles } from "../review/diff-generator.js";
7
7
  import { evaluateTddPolicy } from "../review/tdd-policy.js";
8
8
  import { validateReviewResult } from "../review/schema.js";
9
9
  import { filterReviewScope, buildDeferredContext } from "../review/scope-filter.js";
@@ -198,7 +198,8 @@ export async function runRefactorerStage({ refactorerRole, config, logger, emitt
198
198
  export async function runTddCheckStage({ config, logger, emitter, eventBase, session, trackBudget, iteration, askQuestion }) {
199
199
  logger.setContext({ iteration, stage: "tdd" });
200
200
  const tddDiff = await generateDiff({ baseRef: session.session_start_sha });
201
- const tddEval = evaluateTddPolicy(tddDiff, config.development);
201
+ const untrackedFiles = await getUntrackedFiles();
202
+ const tddEval = evaluateTddPolicy(tddDiff, config.development, untrackedFiles);
202
203
  await addCheckpoint(session, {
203
204
  stage: "tdd-policy",
204
205
  iteration,
@@ -227,34 +228,42 @@ export async function runTddCheckStage({ config, logger, emitter, eventBase, ses
227
228
  session.repeated_issue_count += 1;
228
229
  await saveSession(session);
229
230
  if (session.repeated_issue_count >= config.session.fail_fast_repeats) {
230
- const question = `TDD policy has failed ${session.repeated_issue_count} times. The coder is not creating tests. How should we proceed? Issue: ${tddEval.reason}`;
231
- if (askQuestion) {
232
- const answer = await askQuestion(question, { iteration, stage: "tdd" });
233
- if (answer) {
234
- session.last_reviewer_feedback += `\nUser guidance: ${answer}`;
235
- session.repeated_issue_count = 0;
236
- await saveSession(session);
237
- return { action: "continue" };
238
- }
239
- }
240
- await pauseSession(session, {
241
- question,
242
- context: {
243
- iteration,
244
- stage: "tdd",
245
- lastFeedback: tddEval.message,
246
- repeatedCount: session.repeated_issue_count
247
- }
248
- });
249
231
  emitProgress(
250
232
  emitter,
251
- makeEvent("question", { ...eventBase, stage: "tdd" }, {
252
- status: "paused",
253
- message: question,
254
- detail: { question, sessionId: session.id }
233
+ makeEvent("solomon:escalate", { ...eventBase, stage: "tdd" }, {
234
+ message: `TDD sub-loop limit reached (${session.repeated_issue_count}/${config.session.fail_fast_repeats})`,
235
+ detail: { subloop: "tdd", retryCount: session.repeated_issue_count, reason: tddEval.reason }
255
236
  })
256
237
  );
257
- return { action: "pause", result: { paused: true, sessionId: session.id, question, context: "tdd_fail_fast" } };
238
+
239
+ const solomonResult = await invokeSolomon({
240
+ config, logger, emitter, eventBase, stage: "tdd", askQuestion, session, iteration,
241
+ conflict: {
242
+ stage: "tdd",
243
+ task: session.task,
244
+ iterationCount: session.repeated_issue_count,
245
+ maxIterations: config.session.fail_fast_repeats,
246
+ reason: tddEval.reason,
247
+ sourceFiles: tddEval.sourceFiles,
248
+ testFiles: tddEval.testFiles,
249
+ history: [{ agent: "tdd-policy", feedback: tddEval.message }]
250
+ }
251
+ });
252
+
253
+ if (solomonResult.action === "pause") {
254
+ return { action: "pause", result: { paused: true, sessionId: session.id, question: solomonResult.question, context: "tdd_fail_fast" } };
255
+ }
256
+ if (solomonResult.action === "continue") {
257
+ if (solomonResult.humanGuidance) {
258
+ session.last_reviewer_feedback += `\nUser guidance: ${solomonResult.humanGuidance}`;
259
+ }
260
+ session.repeated_issue_count = 0;
261
+ await saveSession(session);
262
+ return { action: "continue" };
263
+ }
264
+ if (solomonResult.action === "subtask") {
265
+ return { action: "pause", result: { paused: true, sessionId: session.id, subtask: solomonResult.subtask, context: "tdd_subtask" } };
266
+ }
258
267
  }
259
268
  return { action: "continue" };
260
269
  }
@@ -22,6 +22,7 @@ import {
22
22
  incrementalPush
23
23
  } from "./git/automation.js";
24
24
  import { resolveRoleMdPath, loadFirstExisting } from "./roles/base-role.js";
25
+ import { applyPolicies } from "./guards/policy-resolver.js";
25
26
  import { resolveReviewProfile } from "./review/profiles.js";
26
27
  import { CoderRole } from "./roles/coder-role.js";
27
28
  import { invokeSolomon } from "./orchestrator/solomon-escalation.js";
@@ -47,6 +48,10 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
47
48
 
48
49
  // --- Dry-run: return summary without executing anything ---
49
50
  if (flags.dryRun) {
51
+ const dryRunPolicies = applyPolicies({
52
+ taskType: flags.taskType || config.taskType || null,
53
+ policies: config.policies,
54
+ });
50
55
  const projectDir = config.projectDir || process.cwd();
51
56
  const { rules: reviewRules } = await resolveReviewProfile({ mode: config.review_mode, projectDir });
52
57
  const coderRules = await loadFirstExisting(resolveRoleMdPath("coder", projectDir));
@@ -56,6 +61,7 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
56
61
  const summary = {
57
62
  dry_run: true,
58
63
  task,
64
+ policies: dryRunPolicies,
59
65
  roles: {
60
66
  planner: plannerRole,
61
67
  coder: coderRole,
@@ -275,6 +281,32 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
275
281
  if (flags.enableTester !== undefined) testerEnabled = Boolean(flags.enableTester);
276
282
  if (flags.enableSecurity !== undefined) securityEnabled = Boolean(flags.enableSecurity);
277
283
 
284
+ // --- Policy resolver: gate stages by taskType ---
285
+ const resolvedPolicies = applyPolicies({
286
+ taskType: flags.taskType || config.taskType || null,
287
+ policies: config.policies,
288
+ });
289
+ session.resolved_policies = resolvedPolicies;
290
+
291
+ // Apply policy gates on shallow copies (never mutate the caller's config)
292
+ if (!resolvedPolicies.tdd) {
293
+ config = { ...config, development: { ...config.development, methodology: "standard", require_test_changes: false } };
294
+ }
295
+ if (!resolvedPolicies.sonar) {
296
+ config = { ...config, sonarqube: { ...config.sonarqube, enabled: false } };
297
+ }
298
+ if (!resolvedPolicies.reviewer) {
299
+ reviewerEnabled = false;
300
+ }
301
+
302
+ emitProgress(
303
+ emitter,
304
+ makeEvent("policies:resolved", eventBase, {
305
+ message: `Policies resolved for taskType="${resolvedPolicies.taskType}"`,
306
+ detail: resolvedPolicies
307
+ })
308
+ );
309
+
278
310
  // --- Researcher (pre-planning) ---
279
311
  let researchContext = null;
280
312
  if (researcherEnabled) {
@@ -20,3 +20,9 @@ export async function generateDiff({ baseRef }) {
20
20
  }
21
21
  return result.stdout;
22
22
  }
23
+
24
+ export async function getUntrackedFiles() {
25
+ const result = await runCommand("git", ["ls-files", "--others", "--exclude-standard"]);
26
+ if (result.exitCode !== 0) return [];
27
+ return result.stdout.trim().split("\n").filter(Boolean);
28
+ }
@@ -19,13 +19,15 @@ function isSourceFile(file, extensions = []) {
19
19
  return extensions.some((ext) => file.endsWith(ext));
20
20
  }
21
21
 
22
- export function evaluateTddPolicy(diff, developmentConfig = {}) {
22
+ export function evaluateTddPolicy(diff, developmentConfig = {}, untrackedFiles = []) {
23
23
  const requireTestChanges = developmentConfig.require_test_changes !== false;
24
24
  const patterns = developmentConfig.test_file_patterns || ["/tests/", "/__tests__/", ".test.", ".spec."];
25
25
  const extensions =
26
26
  developmentConfig.source_file_extensions || [".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".java", ".rb", ".php", ".cs"];
27
27
 
28
- const files = extractChangedFiles(diff);
28
+ const diffFiles = extractChangedFiles(diff);
29
+ const extra = Array.isArray(untrackedFiles) ? untrackedFiles : [];
30
+ const files = [...new Set([...diffFiles, ...extra])];
29
31
  const sourceFiles = files.filter((f) => isSourceFile(f, extensions) && !isTestFile(f, patterns));
30
32
  const testFiles = files.filter((f) => isTestFile(f, patterns));
31
33