karajan-code 1.20.0 → 1.21.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/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/config.js +15 -0
- package/src/mcp/server-handlers.js +157 -1
- package/src/mcp/tools.js +38 -0
- package/src/orchestrator/iteration-stages.js +41 -0
- package/src/orchestrator.js +10 -1
- package/src/sonar/cloud-scanner.js +76 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -97,6 +97,8 @@ program
|
|
|
97
97
|
.option("--methodology <name>")
|
|
98
98
|
.option("--no-auto-rebase")
|
|
99
99
|
.option("--no-sonar")
|
|
100
|
+
.option("--enable-sonarcloud", "Enable SonarCloud scan (complementary to SonarQube)")
|
|
101
|
+
.option("--no-sonarcloud")
|
|
100
102
|
.option("--checkpoint-interval <n>", "Minutes between interactive checkpoints (default: 5)")
|
|
101
103
|
.option("--pg-task <cardId>", "Planning Game card ID (e.g., KJC-TSK-0042)")
|
|
102
104
|
.option("--pg-project <projectId>", "Planning Game project ID")
|
package/src/config.js
CHANGED
|
@@ -101,6 +101,18 @@ const DEFAULTS = {
|
|
|
101
101
|
disabled_rules: ["javascript:S1116", "javascript:S3776"]
|
|
102
102
|
}
|
|
103
103
|
},
|
|
104
|
+
sonarcloud: {
|
|
105
|
+
enabled: false,
|
|
106
|
+
organization: null,
|
|
107
|
+
token: null,
|
|
108
|
+
project_key: null,
|
|
109
|
+
host: "https://sonarcloud.io",
|
|
110
|
+
scanner: {
|
|
111
|
+
sources: "src,public,lib",
|
|
112
|
+
exclusions: "**/node_modules/**,**/dist/**,**/build/**,**/*.min.js",
|
|
113
|
+
test_inclusions: "**/*.test.js,**/*.spec.js,**/tests/**,**/__tests__/**"
|
|
114
|
+
}
|
|
115
|
+
},
|
|
104
116
|
policies: {},
|
|
105
117
|
serena: { enabled: false },
|
|
106
118
|
planning_game: { enabled: false, project_id: null, codeveloper: null },
|
|
@@ -347,6 +359,9 @@ function applyBecariaOverride(out, flags) {
|
|
|
347
359
|
|
|
348
360
|
function applyMiscOverrides(out, flags) {
|
|
349
361
|
if (flags.noSonar || flags.sonar === false) out.sonarqube.enabled = false;
|
|
362
|
+
out.sonarcloud = out.sonarcloud || {};
|
|
363
|
+
if (flags.enableSonarcloud === true) out.sonarcloud.enabled = true;
|
|
364
|
+
if (flags.noSonarcloud === true || flags.sonarcloud === false) out.sonarcloud.enabled = false;
|
|
350
365
|
|
|
351
366
|
out.planning_game = out.planning_game || {};
|
|
352
367
|
if (flags.pgTask) out.planning_game.enabled = true;
|
|
@@ -469,6 +469,138 @@ export async function handleDiscoverDirect(a, server, extra) {
|
|
|
469
469
|
return { ok: true, ...result.result, summary: result.summary };
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
+
export async function handleTriageDirect(a, server, extra) {
|
|
473
|
+
const config = await buildConfig(a, "triage");
|
|
474
|
+
const logger = createLogger(config.output.log_level, "mcp");
|
|
475
|
+
|
|
476
|
+
const triageRole = resolveRole(config, "triage");
|
|
477
|
+
await assertAgentsAvailable([triageRole.provider]);
|
|
478
|
+
|
|
479
|
+
const projectDir = await resolveProjectDir(server);
|
|
480
|
+
const runLog = createRunLog(projectDir);
|
|
481
|
+
runLog.logText(`[kj_triage] started`);
|
|
482
|
+
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
483
|
+
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
484
|
+
const onOutput = ({ stream, line }) => {
|
|
485
|
+
emitter.emit("progress", { type: "agent:output", stage: "triage", message: line, detail: { stream, agent: triageRole.provider } });
|
|
486
|
+
};
|
|
487
|
+
const stallDetector = createStallDetector({
|
|
488
|
+
onOutput, emitter, eventBase, stage: "triage", provider: triageRole.provider
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
const { TriageRole } = await import("../roles/triage-role.js");
|
|
492
|
+
const triage = new TriageRole({ config, logger, emitter });
|
|
493
|
+
await triage.init({ task: a.task });
|
|
494
|
+
|
|
495
|
+
sendTrackerLog(server, "triage", "running", triageRole.provider);
|
|
496
|
+
runLog.logText(`[triage] agent launched, waiting for response...`);
|
|
497
|
+
let result;
|
|
498
|
+
try {
|
|
499
|
+
result = await triage.run({ task: a.task, onOutput: stallDetector.onOutput });
|
|
500
|
+
} finally {
|
|
501
|
+
stallDetector.stop();
|
|
502
|
+
const stats = stallDetector.stats();
|
|
503
|
+
runLog.logText(`[triage] finished — lines=${stats.lineCount}, bytes=${stats.bytesReceived}, elapsed=${Math.round(stats.elapsedMs / 1000)}s`);
|
|
504
|
+
runLog.close();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (!result.ok) {
|
|
508
|
+
sendTrackerLog(server, "triage", "failed");
|
|
509
|
+
throw new Error(result.result?.error || result.summary || "Triage failed");
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
sendTrackerLog(server, "triage", "done");
|
|
513
|
+
return { ok: true, ...result.result, summary: result.summary };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export async function handleResearcherDirect(a, server, extra) {
|
|
517
|
+
const config = await buildConfig(a, "researcher");
|
|
518
|
+
const logger = createLogger(config.output.log_level, "mcp");
|
|
519
|
+
|
|
520
|
+
const researcherRole = resolveRole(config, "researcher");
|
|
521
|
+
await assertAgentsAvailable([researcherRole.provider]);
|
|
522
|
+
|
|
523
|
+
const projectDir = await resolveProjectDir(server);
|
|
524
|
+
const runLog = createRunLog(projectDir);
|
|
525
|
+
runLog.logText(`[kj_researcher] started`);
|
|
526
|
+
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
527
|
+
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
528
|
+
const onOutput = ({ stream, line }) => {
|
|
529
|
+
emitter.emit("progress", { type: "agent:output", stage: "researcher", message: line, detail: { stream, agent: researcherRole.provider } });
|
|
530
|
+
};
|
|
531
|
+
const stallDetector = createStallDetector({
|
|
532
|
+
onOutput, emitter, eventBase, stage: "researcher", provider: researcherRole.provider
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
const { ResearcherRole } = await import("../roles/researcher-role.js");
|
|
536
|
+
const researcher = new ResearcherRole({ config, logger, emitter });
|
|
537
|
+
await researcher.init({ task: a.task });
|
|
538
|
+
|
|
539
|
+
sendTrackerLog(server, "researcher", "running", researcherRole.provider);
|
|
540
|
+
runLog.logText(`[researcher] agent launched, waiting for response...`);
|
|
541
|
+
let result;
|
|
542
|
+
try {
|
|
543
|
+
result = await researcher.run({ task: a.task, onOutput: stallDetector.onOutput });
|
|
544
|
+
} finally {
|
|
545
|
+
stallDetector.stop();
|
|
546
|
+
const stats = stallDetector.stats();
|
|
547
|
+
runLog.logText(`[researcher] finished — lines=${stats.lineCount}, bytes=${stats.bytesReceived}, elapsed=${Math.round(stats.elapsedMs / 1000)}s`);
|
|
548
|
+
runLog.close();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (!result.ok) {
|
|
552
|
+
sendTrackerLog(server, "researcher", "failed");
|
|
553
|
+
throw new Error(result.result?.error || result.summary || "Researcher failed");
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
sendTrackerLog(server, "researcher", "done");
|
|
557
|
+
return { ok: true, ...result.result, summary: result.summary };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export async function handleArchitectDirect(a, server, extra) {
|
|
561
|
+
const config = await buildConfig(a, "architect");
|
|
562
|
+
const logger = createLogger(config.output.log_level, "mcp");
|
|
563
|
+
|
|
564
|
+
const architectRole = resolveRole(config, "architect");
|
|
565
|
+
await assertAgentsAvailable([architectRole.provider]);
|
|
566
|
+
|
|
567
|
+
const projectDir = await resolveProjectDir(server);
|
|
568
|
+
const runLog = createRunLog(projectDir);
|
|
569
|
+
runLog.logText(`[kj_architect] started`);
|
|
570
|
+
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
571
|
+
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
572
|
+
const onOutput = ({ stream, line }) => {
|
|
573
|
+
emitter.emit("progress", { type: "agent:output", stage: "architect", message: line, detail: { stream, agent: architectRole.provider } });
|
|
574
|
+
};
|
|
575
|
+
const stallDetector = createStallDetector({
|
|
576
|
+
onOutput, emitter, eventBase, stage: "architect", provider: architectRole.provider
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const { ArchitectRole } = await import("../roles/architect-role.js");
|
|
580
|
+
const architect = new ArchitectRole({ config, logger, emitter });
|
|
581
|
+
await architect.init({ task: a.task });
|
|
582
|
+
|
|
583
|
+
sendTrackerLog(server, "architect", "running", architectRole.provider);
|
|
584
|
+
runLog.logText(`[architect] agent launched, waiting for response...`);
|
|
585
|
+
let result;
|
|
586
|
+
try {
|
|
587
|
+
result = await architect.run({ task: a.task, researchContext: a.context || null, onOutput: stallDetector.onOutput });
|
|
588
|
+
} finally {
|
|
589
|
+
stallDetector.stop();
|
|
590
|
+
const stats = stallDetector.stats();
|
|
591
|
+
runLog.logText(`[architect] finished — lines=${stats.lineCount}, bytes=${stats.bytesReceived}, elapsed=${Math.round(stats.elapsedMs / 1000)}s`);
|
|
592
|
+
runLog.close();
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (!result.ok) {
|
|
596
|
+
sendTrackerLog(server, "architect", "failed");
|
|
597
|
+
throw new Error(result.result?.error || result.summary || "Architect failed");
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
sendTrackerLog(server, "architect", "done");
|
|
601
|
+
return { ok: true, ...result.result, summary: result.summary };
|
|
602
|
+
}
|
|
603
|
+
|
|
472
604
|
/* ── Preflight helpers ─────────────────────────────────────────────── */
|
|
473
605
|
|
|
474
606
|
const AGENT_ROLES = new Set(["coder", "reviewer", "tester", "security", "solomon"]);
|
|
@@ -662,6 +794,27 @@ async function handleDiscover(a, server, extra) {
|
|
|
662
794
|
return handleDiscoverDirect(a, server, extra);
|
|
663
795
|
}
|
|
664
796
|
|
|
797
|
+
async function handleTriage(a, server, extra) {
|
|
798
|
+
if (!a.task) {
|
|
799
|
+
return failPayload("Missing required field: task");
|
|
800
|
+
}
|
|
801
|
+
return handleTriageDirect(a, server, extra);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
async function handleResearcher(a, server, extra) {
|
|
805
|
+
if (!a.task) {
|
|
806
|
+
return failPayload("Missing required field: task");
|
|
807
|
+
}
|
|
808
|
+
return handleResearcherDirect(a, server, extra);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
async function handleArchitect(a, server, extra) {
|
|
812
|
+
if (!a.task) {
|
|
813
|
+
return failPayload("Missing required field: task");
|
|
814
|
+
}
|
|
815
|
+
return handleArchitectDirect(a, server, extra);
|
|
816
|
+
}
|
|
817
|
+
|
|
665
818
|
/* ── Handler dispatch map ─────────────────────────────────────────── */
|
|
666
819
|
|
|
667
820
|
const toolHandlers = {
|
|
@@ -679,7 +832,10 @@ const toolHandlers = {
|
|
|
679
832
|
kj_code: (a, server, extra) => handleCode(a, server, extra),
|
|
680
833
|
kj_review: (a, server, extra) => handleReview(a, server, extra),
|
|
681
834
|
kj_plan: (a, server, extra) => handlePlan(a, server, extra),
|
|
682
|
-
kj_discover:
|
|
835
|
+
kj_discover: (a, server, extra) => handleDiscover(a, server, extra),
|
|
836
|
+
kj_triage: (a, server, extra) => handleTriage(a, server, extra),
|
|
837
|
+
kj_researcher: (a, server, extra) => handleResearcher(a, server, extra),
|
|
838
|
+
kj_architect: (a, server, extra) => handleArchitect(a, server, extra)
|
|
683
839
|
};
|
|
684
840
|
|
|
685
841
|
export async function handleToolCall(name, args, server, extra) {
|
package/src/mcp/tools.js
CHANGED
|
@@ -93,6 +93,7 @@ export const tools = [
|
|
|
93
93
|
checkpointInterval: { type: "number", description: "Minutes between interactive checkpoints (default: 5). Set 0 to disable." },
|
|
94
94
|
taskType: { type: "string", enum: ["sw", "infra", "doc", "add-tests", "refactor"], description: "Explicit task type for policy resolution. Overrides triage classification." },
|
|
95
95
|
noSonar: { type: "boolean" },
|
|
96
|
+
enableSonarcloud: { type: "boolean", description: "Enable SonarCloud scan (complementary to SonarQube)" },
|
|
96
97
|
kjHome: { type: "string" },
|
|
97
98
|
sonarToken: { type: "string" },
|
|
98
99
|
timeoutMs: { type: "number" }
|
|
@@ -242,5 +243,42 @@ export const tools = [
|
|
|
242
243
|
kjHome: { type: "string" }
|
|
243
244
|
}
|
|
244
245
|
}
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: "kj_triage",
|
|
249
|
+
description: "Classify task complexity and recommend which pipeline roles to activate. Returns level (trivial/simple/medium/complex), taskType, recommended roles, and optional decomposition.",
|
|
250
|
+
inputSchema: {
|
|
251
|
+
type: "object",
|
|
252
|
+
required: ["task"],
|
|
253
|
+
properties: {
|
|
254
|
+
task: { type: "string", description: "Task description to classify" },
|
|
255
|
+
kjHome: { type: "string" }
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "kj_researcher",
|
|
261
|
+
description: "Research the codebase for a task. Identifies affected files, patterns, constraints, prior decisions, risks, and test coverage.",
|
|
262
|
+
inputSchema: {
|
|
263
|
+
type: "object",
|
|
264
|
+
required: ["task"],
|
|
265
|
+
properties: {
|
|
266
|
+
task: { type: "string", description: "Task description to research" },
|
|
267
|
+
kjHome: { type: "string" }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "kj_architect",
|
|
273
|
+
description: "Design solution architecture for a task. Returns layers, patterns, data model, API contracts, tradeoffs, and a verdict (ready/needs_clarification).",
|
|
274
|
+
inputSchema: {
|
|
275
|
+
type: "object",
|
|
276
|
+
required: ["task"],
|
|
277
|
+
properties: {
|
|
278
|
+
task: { type: "string", description: "Task description to architect" },
|
|
279
|
+
context: { type: "string", description: "Additional context (e.g., researcher output)" },
|
|
280
|
+
kjHome: { type: "string" }
|
|
281
|
+
}
|
|
282
|
+
}
|
|
245
283
|
}
|
|
246
284
|
];
|
|
@@ -427,6 +427,47 @@ export async function runSonarStage({ config, logger, emitter, eventBase, sessio
|
|
|
427
427
|
return { action: "ok", stageResult };
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
+
export async function runSonarCloudStage({ config, logger, emitter, eventBase, session, trackBudget, iteration }) {
|
|
431
|
+
logger.setContext({ iteration, stage: "sonarcloud" });
|
|
432
|
+
emitProgress(
|
|
433
|
+
emitter,
|
|
434
|
+
makeEvent("sonarcloud:start", { ...eventBase, stage: "sonarcloud" }, {
|
|
435
|
+
message: "SonarCloud scanning"
|
|
436
|
+
})
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
const { runSonarCloudScan } = await import("../sonar/cloud-scanner.js");
|
|
440
|
+
const scanStart = Date.now();
|
|
441
|
+
const result = await runSonarCloudScan(config);
|
|
442
|
+
trackBudget({ role: "sonarcloud", provider: "sonarcloud", result: { ok: result.ok }, duration_ms: Date.now() - scanStart });
|
|
443
|
+
|
|
444
|
+
await addCheckpoint(session, {
|
|
445
|
+
stage: "sonarcloud",
|
|
446
|
+
iteration,
|
|
447
|
+
project_key: result.projectKey,
|
|
448
|
+
exitCode: result.exitCode,
|
|
449
|
+
provider: "sonarcloud",
|
|
450
|
+
model: null
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const status = result.ok ? "ok" : "warn";
|
|
454
|
+
const message = result.ok
|
|
455
|
+
? `SonarCloud scan passed (project: ${result.projectKey})`
|
|
456
|
+
: `SonarCloud scan issue: ${(result.stderr || "").slice(0, 200)}`;
|
|
457
|
+
|
|
458
|
+
emitProgress(
|
|
459
|
+
emitter,
|
|
460
|
+
makeEvent("sonarcloud:end", { ...eventBase, stage: "sonarcloud" }, {
|
|
461
|
+
status,
|
|
462
|
+
message,
|
|
463
|
+
detail: { projectKey: result.projectKey, exitCode: result.exitCode }
|
|
464
|
+
})
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
// SonarCloud is advisory — never blocks the pipeline
|
|
468
|
+
return { action: "ok", stageResult: { ok: result.ok, projectKey: result.projectKey, message } };
|
|
469
|
+
}
|
|
470
|
+
|
|
430
471
|
async function handleReviewerStalledSolomon({ review, repeatCounts, repeatState, config, logger, emitter, eventBase, session, iteration, task, askQuestion, budgetSummary, repeatDetector }) {
|
|
431
472
|
logger.warn(`Reviewer stalled (${repeatCounts.reviewer} repeats). Invoking Solomon mediation.`);
|
|
432
473
|
emitProgress(
|
package/src/orchestrator.js
CHANGED
|
@@ -30,7 +30,7 @@ import { resolveReviewProfile } from "./review/profiles.js";
|
|
|
30
30
|
import { CoderRole } from "./roles/coder-role.js";
|
|
31
31
|
import { invokeSolomon } from "./orchestrator/solomon-escalation.js";
|
|
32
32
|
import { runTriageStage, runResearcherStage, runArchitectStage, runPlannerStage, runDiscoverStage } from "./orchestrator/pre-loop-stages.js";
|
|
33
|
-
import { runCoderStage, runRefactorerStage, runTddCheckStage, runSonarStage, runReviewerStage } from "./orchestrator/iteration-stages.js";
|
|
33
|
+
import { runCoderStage, runRefactorerStage, runTddCheckStage, runSonarStage, runSonarCloudStage, runReviewerStage } from "./orchestrator/iteration-stages.js";
|
|
34
34
|
import { runTesterStage, runSecurityStage } from "./orchestrator/post-loop-stages.js";
|
|
35
35
|
import { waitForCooldown, MAX_STANDBY_RETRIES } from "./orchestrator/standby.js";
|
|
36
36
|
|
|
@@ -910,6 +910,15 @@ async function runQualityGateStages({ config, logger, emitter, eventBase, sessio
|
|
|
910
910
|
}
|
|
911
911
|
}
|
|
912
912
|
|
|
913
|
+
if (config.sonarcloud?.enabled) {
|
|
914
|
+
const cloudResult = await runSonarCloudStage({
|
|
915
|
+
config, logger, emitter, eventBase, session, trackBudget, iteration: i
|
|
916
|
+
});
|
|
917
|
+
if (cloudResult.stageResult) {
|
|
918
|
+
stageResults.sonarcloud = cloudResult.stageResult;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
913
922
|
return { action: "ok" };
|
|
914
923
|
}
|
|
915
924
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { runCommand } from "../utils/process.js";
|
|
2
|
+
import { resolveSonarProjectKey } from "./project-key.js";
|
|
3
|
+
|
|
4
|
+
function buildCloudScannerArgs(projectKey, config) {
|
|
5
|
+
const sc = config.sonarcloud || {};
|
|
6
|
+
const scanner = sc.scanner || {};
|
|
7
|
+
const host = sc.host || "https://sonarcloud.io";
|
|
8
|
+
const token = process.env.KJ_SONARCLOUD_TOKEN || sc.token;
|
|
9
|
+
const organization = process.env.KJ_SONARCLOUD_ORG || sc.organization;
|
|
10
|
+
|
|
11
|
+
const args = [
|
|
12
|
+
`-Dsonar.host.url=${host}`,
|
|
13
|
+
`-Dsonar.projectKey=${projectKey}`
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
if (token) args.push(`-Dsonar.token=${token}`);
|
|
17
|
+
if (organization) args.push(`-Dsonar.organization=${organization}`);
|
|
18
|
+
if (scanner.sources) args.push(`-Dsonar.sources=${scanner.sources}`);
|
|
19
|
+
if (scanner.exclusions) args.push(`-Dsonar.exclusions=${scanner.exclusions}`);
|
|
20
|
+
if (scanner.test_inclusions) args.push(`-Dsonar.test.inclusions=${scanner.test_inclusions}`);
|
|
21
|
+
|
|
22
|
+
return args;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function runSonarCloudScan(config, projectKey = null) {
|
|
26
|
+
const sc = config.sonarcloud || {};
|
|
27
|
+
const token = process.env.KJ_SONARCLOUD_TOKEN || sc.token;
|
|
28
|
+
const organization = sc.organization || process.env.KJ_SONARCLOUD_ORG;
|
|
29
|
+
|
|
30
|
+
if (!token) {
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
projectKey: null,
|
|
34
|
+
stdout: "",
|
|
35
|
+
stderr: "SonarCloud token not configured. Set sonarcloud.token in kj.config.yml or KJ_SONARCLOUD_TOKEN env var.",
|
|
36
|
+
exitCode: 1
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!organization) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
projectKey: null,
|
|
44
|
+
stdout: "",
|
|
45
|
+
stderr: "SonarCloud organization not configured. Set sonarcloud.organization in kj.config.yml or KJ_SONARCLOUD_ORG env var.",
|
|
46
|
+
exitCode: 1
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let effectiveProjectKey;
|
|
51
|
+
try {
|
|
52
|
+
effectiveProjectKey = projectKey || sc.project_key || await resolveSonarProjectKey(config, { projectKey });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
projectKey: null,
|
|
57
|
+
stdout: "",
|
|
58
|
+
stderr: error?.message || String(error),
|
|
59
|
+
exitCode: 1
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const scannerTimeout = 15 * 60 * 1000;
|
|
64
|
+
const args = buildCloudScannerArgs(effectiveProjectKey, config);
|
|
65
|
+
|
|
66
|
+
// Use npx @sonar/scan (no Docker needed)
|
|
67
|
+
const result = await runCommand("npx", ["@sonar/scan", ...args], { timeout: scannerTimeout });
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
ok: result.exitCode === 0,
|
|
71
|
+
projectKey: effectiveProjectKey,
|
|
72
|
+
stdout: result.stdout,
|
|
73
|
+
stderr: result.stderr,
|
|
74
|
+
exitCode: result.exitCode
|
|
75
|
+
};
|
|
76
|
+
}
|