karajan-code 1.31.1 → 1.32.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/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="docs/karajan-code-logo-small.png" alt="Karajan Code" width="200">
2
+ <img src="docs/karajan-code-logo.svg" alt="Karajan Code" width="180">
3
3
  </p>
4
4
 
5
5
  <h1 align="center">Karajan Code</h1>
@@ -46,6 +46,7 @@ Use Karajan when you want:
46
46
  - **Zero-config operation** — auto-detects test frameworks, starts SonarQube, simplifies pipeline for trivial tasks
47
47
  - **Composable role architecture** — define agent behaviors as plain markdown files that travel with your project
48
48
  - **Local-first** — your code, your keys, your machine, no data leaves unless you say so
49
+ - **Zero API costs** — Karajan uses AI agent CLIs (Claude Code, Codex, Gemini CLI), not APIs. You pay your existing subscription (Claude Pro, ChatGPT Plus), not per-token API fees. No surprise bills.
49
50
 
50
51
  If Claude Code is a smart pair programmer, Karajan is the CI/CD pipeline for AI-assisted development. They work great together — Karajan is designed to be used as an MCP server inside Claude Code.
51
52
 
@@ -64,6 +65,8 @@ That's it. No Docker required (SonarQube uses Docker, but Karajan auto-manages i
64
65
  kj run "Create a utility function that validates Spanish DNI numbers, with tests"
65
66
  ```
66
67
 
68
+ [**▶ Watch the full pipeline demo**](https://karajancode.com#demo) — HU certification, triage, architecture, TDD, SonarQube, code review, Solomon arbitration, security audit.
69
+
67
70
  Karajan will:
68
71
  1. Triage the task complexity and activate the right roles
69
72
  2. Write tests first (TDD)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "1.31.1",
3
+ "version": "1.32.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",
@@ -1,6 +1,7 @@
1
1
  import { TesterRole } from "../roles/tester-role.js";
2
2
  import { SecurityRole } from "../roles/security-role.js";
3
3
  import { ImpeccableRole } from "../roles/impeccable-role.js";
4
+ import { AuditRole } from "../roles/audit-role.js";
4
5
  import { addCheckpoint, saveSession } from "../session-store.js";
5
6
  import { emitProgress, makeEvent } from "../utils/events.js";
6
7
  import { invokeSolomon } from "./solomon-escalation.js";
@@ -290,5 +291,113 @@ export async function runImpeccableStage({ config, logger, emitter, eventBase, s
290
291
  return { action: "ok", stageResult: { ok: impeccableOutput.ok, verdict, summary: impeccableOutput.summary || "No frontend design issues found" } };
291
292
  }
292
293
 
294
+ export async function runFinalAuditStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget, iteration, task, diff }) {
295
+ logger.setContext({ iteration, stage: "audit" });
296
+ emitProgress(
297
+ emitter,
298
+ makeEvent("audit:start", { ...eventBase, stage: "audit" }, {
299
+ message: "Final audit — verifying code quality"
300
+ })
301
+ );
302
+
303
+ const auditStart = Date.now();
304
+ const { output: auditOutput, provider, attempts } = await runRoleWithFallback(
305
+ AuditRole,
306
+ { roleName: "audit", config, logger, emitter, eventBase, task, iteration, diff }
307
+ );
308
+ const totalDuration = Date.now() - auditStart;
309
+
310
+ trackBudget({
311
+ role: "audit",
312
+ provider: provider || coderRole.provider,
313
+ model: config?.roles?.audit?.model || coderRole.model,
314
+ result: auditOutput,
315
+ duration_ms: totalDuration
316
+ });
317
+
318
+ await addCheckpoint(session, {
319
+ stage: "audit",
320
+ iteration,
321
+ ok: auditOutput.ok,
322
+ provider: provider || coderRole.provider,
323
+ model: config?.roles?.audit?.model || coderRole.model || null,
324
+ attempts: attempts.length > 1 ? attempts : undefined
325
+ });
326
+
327
+ if (!auditOutput.ok) {
328
+ // Audit agent failed to run — treat as advisory, don't block pipeline
329
+ logger.warn(`Audit agent error (advisory): ${auditOutput.summary}`);
330
+ emitProgress(
331
+ emitter,
332
+ makeEvent("audit:end", { ...eventBase, stage: "audit" }, {
333
+ status: "warn",
334
+ message: `Audit: agent error (advisory), continuing — ${auditOutput.summary}`
335
+ })
336
+ );
337
+ return { action: "ok", stageResult: { ok: false, summary: auditOutput.summary || "Audit agent error (advisory)", auto_continued: true } };
338
+ }
339
+
340
+ // Parse findings from audit result
341
+ const result = auditOutput.result || {};
342
+ const summary = result.summary || {};
343
+ const overallHealth = summary.overallHealth || "fair";
344
+ const criticalCount = summary.critical || 0;
345
+ const highCount = summary.high || 0;
346
+
347
+ // Collect critical and high findings for feedback
348
+ const actionableFindings = [];
349
+ if (result.dimensions) {
350
+ for (const [dimName, dim] of Object.entries(result.dimensions)) {
351
+ for (const finding of (dim.findings || [])) {
352
+ if (finding.severity === "critical" || finding.severity === "high") {
353
+ actionableFindings.push({
354
+ dimension: dimName,
355
+ ...finding
356
+ });
357
+ }
358
+ }
359
+ }
360
+ }
361
+
362
+ const hasActionableIssues = (overallHealth === "poor" || overallHealth === "critical") && (criticalCount > 0 || highCount > 0);
363
+
364
+ if (hasActionableIssues) {
365
+ // Build feedback string for the coder
366
+ const feedbackLines = actionableFindings.map(f => {
367
+ const loc = f.file ? `${f.file}${f.line ? `:${f.line}` : ""}` : "";
368
+ return `[${f.severity.toUpperCase()}] ${loc} ${f.description}${f.recommendation ? ` — Fix: ${f.recommendation}` : ""}`;
369
+ });
370
+ const feedback = `Audit found ${criticalCount + highCount} critical/high issue(s) that must be fixed:\n${feedbackLines.join("\n")}`;
371
+
372
+ logger.warn(`Audit: ${criticalCount + highCount} actionable issues found, sending back to coder`);
373
+ emitProgress(
374
+ emitter,
375
+ makeEvent("audit:end", { ...eventBase, stage: "audit" }, {
376
+ status: "fail",
377
+ message: `Audit: ${criticalCount + highCount} issue(s) found, sending back to coder`
378
+ })
379
+ );
380
+
381
+ return { action: "retry", feedback, stageResult: { ok: false, summary: auditOutput.summary || `${criticalCount + highCount} actionable issues` } };
382
+ }
383
+
384
+ // Audit passed (good/fair or no critical/high findings)
385
+ const hasAdvisory = (summary.medium || 0) + (summary.low || 0) > 0;
386
+ const certifiedMsg = hasAdvisory
387
+ ? `Audit: CERTIFIED (with ${(summary.medium || 0) + (summary.low || 0)} advisory warning(s))`
388
+ : "Audit: CERTIFIED";
389
+
390
+ logger.info(certifiedMsg);
391
+ emitProgress(
392
+ emitter,
393
+ makeEvent("audit:end", { ...eventBase, stage: "audit" }, {
394
+ status: "ok",
395
+ message: certifiedMsg
396
+ })
397
+ );
398
+
399
+ return { action: "ok", stageResult: { ok: true, summary: certifiedMsg } };
400
+ }
401
+
293
402
  // Exported for testing
294
403
  export { buildFallbackChain, isAgentFailure, runRoleWithFallback };
@@ -32,7 +32,7 @@ import { invokeSolomon } from "./orchestrator/solomon-escalation.js";
32
32
  import { PipelineContext } from "./orchestrator/pipeline-context.js";
33
33
  import { runTriageStage, runResearcherStage, runArchitectStage, runPlannerStage, runDiscoverStage, runHuReviewerStage } from "./orchestrator/pre-loop-stages.js";
34
34
  import { runCoderStage, runRefactorerStage, runTddCheckStage, runSonarStage, runSonarCloudStage, runReviewerStage } from "./orchestrator/iteration-stages.js";
35
- import { runTesterStage, runSecurityStage, runImpeccableStage } from "./orchestrator/post-loop-stages.js";
35
+ import { runTesterStage, runSecurityStage, runImpeccableStage, runFinalAuditStage } from "./orchestrator/post-loop-stages.js";
36
36
  import { waitForCooldown, MAX_STANDBY_RETRIES } from "./orchestrator/standby.js";
37
37
  import { detectTestFramework } from "./utils/project-detect.js";
38
38
  import { runPreflightChecks } from "./orchestrator/preflight-checks.js";
@@ -669,6 +669,22 @@ async function handlePostLoopStages({ config, session, emitter, eventBase, coder
669
669
  }
670
670
  }
671
671
 
672
+ // Final audit — last quality gate before declaring success
673
+ const auditResult = await runFinalAuditStage({
674
+ config, logger, emitter, eventBase, session, coderRole, trackBudget,
675
+ iteration: i, task, diff: postLoopDiff
676
+ });
677
+ if (auditResult.stageResult) {
678
+ stageResults.audit = auditResult.stageResult;
679
+ await tryBecariaComment({ config, session, logger, agent: "Audit", body: `Final audit: ${auditResult.stageResult.summary || "completed"}` });
680
+ }
681
+ if (auditResult.action === "retry") {
682
+ // Audit found actionable issues — loop back to coder
683
+ session.last_reviewer_feedback = auditResult.feedback;
684
+ await saveSession(session);
685
+ return { action: "continue" };
686
+ }
687
+
672
688
  return { action: "proceed" };
673
689
  }
674
690
 
@@ -1199,7 +1215,7 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
1199
1215
  const checkpointIntervalMs = (ctx.config.session.checkpoint_interval_minutes ?? 5) * 60 * 1000;
1200
1216
  let lastCheckpointAt = Date.now();
1201
1217
  let checkpointDisabled = false;
1202
- let lastCheckpointSnapshot = null;
1218
+ let lastCheckpointSnapshot = takeCheckpointSnapshot(ctx.session);
1203
1219
 
1204
1220
  let i = 0;
1205
1221
  while (i < ctx.config.max_iterations) {