karajan-code 2.7.2 → 2.7.4
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 +4 -4
- package/bin/kj-tail +34 -7
- package/package.json +1 -1
- package/src/checks/node.js +10 -4
- package/src/checks/ports.js +18 -2
- package/src/checks/runner.js +26 -10
- package/src/checks/tokens.js +77 -24
- package/src/cli.js +48 -18
- package/src/commands/architect.js +26 -21
- package/src/commands/audit.js +32 -24
- package/src/commands/code.js +31 -25
- package/src/commands/discover.js +26 -21
- package/src/commands/plan.js +12 -1
- package/src/commands/researcher.js +19 -14
- package/src/commands/review.js +10 -4
- package/src/commands/run.js +31 -21
- package/src/commands/triage.js +27 -21
- package/src/config.js +24 -1
- package/src/mcp/direct-role-runner.js +12 -0
- package/src/mcp/handlers/direct-handlers.js +6 -0
- package/src/mcp/handlers/run-handler.js +7 -1
- package/src/mcp/shared-helpers.js +23 -0
- package/src/mcp/tools.js +18 -16
- package/src/orchestrator/flow-runner.js +62 -4
- package/src/orchestrator/preflight-checks.js +15 -1
- package/src/session-store.js +6 -0
- package/src/utils/cli-run-log.js +83 -0
- package/src/utils/task-file.js +109 -0
package/src/commands/audit.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createAgent } from "../agents/index.js";
|
|
|
2
2
|
import { assertAgentsAvailable } from "../agents/availability.js";
|
|
3
3
|
import { resolveRole } from "../config.js";
|
|
4
4
|
import { buildAuditPrompt, parseAuditOutput, AUDIT_DIMENSIONS } from "../prompts/audit.js";
|
|
5
|
+
import { withCliRunLog } from "../utils/cli-run-log.js";
|
|
5
6
|
|
|
6
7
|
function formatFindings(findings) {
|
|
7
8
|
const lines = [];
|
|
@@ -66,34 +67,41 @@ function formatAudit(parsed) {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export async function auditCommand({ task, config, logger, dimensions, json }) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
return withCliRunLog("audit", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
71
|
+
const auditRole = resolveRole(config, "audit");
|
|
72
|
+
await assertAgentsAvailable([auditRole.provider]);
|
|
73
|
+
logger.info(`Audit (${auditRole.provider}) starting...`);
|
|
74
|
+
runLog.logText(`[audit] provider=${auditRole.provider} dimensions=${dimensions || "all"}`);
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
const agent = createAgent(auditRole.provider, config, logger);
|
|
77
|
+
const dimList = dimensions && dimensions !== "all"
|
|
78
|
+
? dimensions.split(",").map(d => d.trim().toLowerCase()).map(d => d === "quality" ? "codeQuality" : d).filter(d => AUDIT_DIMENSIONS.includes(d))
|
|
79
|
+
: null;
|
|
77
80
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
const prompt = buildAuditPrompt({ task: task || "Analyze the full codebase", dimensions: dimList });
|
|
82
|
+
const onOutput = ({ line }) => process.stdout.write(`${line}\n`);
|
|
83
|
+
const result = await agent.runTask({ prompt, onOutput, role: "audit" });
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
if (!result.ok) {
|
|
86
|
+
throw new Error(result.error || result.output || "Audit failed");
|
|
87
|
+
}
|
|
85
88
|
|
|
86
|
-
|
|
89
|
+
const parsed = parseAuditOutput(result.output);
|
|
90
|
+
if (parsed?.summary) {
|
|
91
|
+
runLog.logText(`[audit] findings=${parsed.summary.totalFindings} (critical=${parsed.summary.critical}, high=${parsed.summary.high})`);
|
|
92
|
+
}
|
|
87
93
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
if (json) {
|
|
95
|
+
console.log(JSON.stringify(parsed || result.output, null, 2));
|
|
96
|
+
return { ok: true };
|
|
97
|
+
}
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
if (parsed?.summary) {
|
|
100
|
+
console.log(formatAudit(parsed));
|
|
101
|
+
} else {
|
|
102
|
+
console.log(result.output);
|
|
103
|
+
}
|
|
104
|
+
logger.info("Audit completed.");
|
|
105
|
+
return { ok: true };
|
|
106
|
+
});
|
|
99
107
|
}
|
package/src/commands/code.js
CHANGED
|
@@ -3,32 +3,38 @@ import { createAgent } from "../agents/index.js";
|
|
|
3
3
|
import { assertAgentsAvailable } from "../agents/availability.js";
|
|
4
4
|
import { buildCoderPrompt } from "../prompts/coder.js";
|
|
5
5
|
import { resolveRole } from "../config.js";
|
|
6
|
+
import { withCliRunLog } from "../utils/cli-run-log.js";
|
|
6
7
|
|
|
7
8
|
export async function codeCommand({ task, config, logger }) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
return withCliRunLog("code", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
10
|
+
const coderRole = resolveRole(config, "coder");
|
|
11
|
+
await assertAgentsAvailable([coderRole.provider]);
|
|
12
|
+
logger.info(`Coder (${coderRole.provider}) starting...`);
|
|
13
|
+
runLog.logText(`[coder] provider=${coderRole.provider}`);
|
|
14
|
+
const coder = createAgent(coderRole.provider, config, logger);
|
|
15
|
+
let coderRules = null;
|
|
16
|
+
if (config.coder_rules) {
|
|
17
|
+
try {
|
|
18
|
+
coderRules = await fs.readFile(config.coder_rules, "utf8");
|
|
19
|
+
} catch { /* configured coder_rules path not found — try fallback */
|
|
20
|
+
try { coderRules = await fs.readFile("coder-rules.md", "utf8"); } catch { /* no coder rules file */ }
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
const prompt = await buildCoderPrompt({ task, coderRules, methodology: config.development?.methodology || "tdd" });
|
|
24
|
+
const onOutput = ({ line }) => process.stdout.write(`${line}\n`);
|
|
25
|
+
const result = await coder.runTask({ prompt, onOutput, role: "coder" });
|
|
26
|
+
if (!result.ok) {
|
|
27
|
+
if (result.error) logger.error(result.error);
|
|
28
|
+
throw new Error(result.error || result.output || `Coder failed (exit ${result.exitCode})`);
|
|
29
|
+
}
|
|
30
|
+
if (result.output) {
|
|
31
|
+
console.log(result.output);
|
|
32
|
+
}
|
|
33
|
+
if (result.error) {
|
|
34
|
+
logger.warn(result.error);
|
|
35
|
+
}
|
|
36
|
+
logger.info(`Coder completed (exit ${result.exitCode})`);
|
|
37
|
+
runLog.logText(`[coder] finished (exit=${result.exitCode})`);
|
|
38
|
+
return { ok: true };
|
|
39
|
+
});
|
|
34
40
|
}
|
package/src/commands/discover.js
CHANGED
|
@@ -62,30 +62,35 @@ function formatDiscover(result, mode) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export async function discoverCommand({ task, config, logger, mode, json }) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
const { withCliRunLog } = await import("../utils/cli-run-log.js");
|
|
66
|
+
return withCliRunLog("discover", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
67
|
+
const discoverRole = resolveRole(config, "discover");
|
|
68
|
+
await assertAgentsAvailable([discoverRole.provider]);
|
|
69
|
+
logger.info(`Discover (${discoverRole.provider}) starting — mode: ${mode || "gaps"}...`);
|
|
70
|
+
runLog.logText(`[discover] provider=${discoverRole.provider} mode=${mode || "gaps"}`);
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
const agent = createAgent(discoverRole.provider, config, logger);
|
|
73
|
+
const prompt = buildDiscoverPrompt({ task, mode: mode || "gaps" });
|
|
74
|
+
const onOutput = ({ line }) => process.stdout.write(`${line}\n`);
|
|
75
|
+
const result = await agent.runTask({ prompt, onOutput, role: "discover" });
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
if (!result.ok) {
|
|
78
|
+
throw new Error(result.error || result.output || "Discover failed");
|
|
79
|
+
}
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
const parsed = parseDiscoverOutput(result.output);
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
if (json) {
|
|
84
|
+
console.log(JSON.stringify(parsed || result.output, null, 2));
|
|
85
|
+
return { ok: true };
|
|
86
|
+
}
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
if (parsed?.verdict) {
|
|
89
|
+
console.log(formatDiscover(parsed, mode || "gaps"));
|
|
90
|
+
} else {
|
|
91
|
+
console.log(result.output);
|
|
92
|
+
}
|
|
93
|
+
logger.info("Discover completed.");
|
|
94
|
+
return { ok: true };
|
|
95
|
+
});
|
|
91
96
|
}
|
package/src/commands/plan.js
CHANGED
|
@@ -51,8 +51,16 @@ function formatHuTable(hus) {
|
|
|
51
51
|
* kj plan "task" — generate plan + HUs
|
|
52
52
|
*/
|
|
53
53
|
export async function planGenerateCommand({ task, config, logger, json, context }) {
|
|
54
|
+
const { withCliRunLog } = await import("../utils/cli-run-log.js");
|
|
55
|
+
return withCliRunLog("plan", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
56
|
+
return planGenerateImpl({ task, config, logger, json, context, runLog });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function planGenerateImpl({ task, config, logger, json, context, runLog }) {
|
|
54
61
|
const plannerRole = resolveRole(config, "planner");
|
|
55
62
|
await assertAgentsAvailable([plannerRole.provider]);
|
|
63
|
+
runLog.logText(`[planner] provider=${plannerRole.provider}`);
|
|
56
64
|
|
|
57
65
|
const planner = createAgent(plannerRole.provider, config, logger);
|
|
58
66
|
const prompt = buildPlannerPrompt({ task, context });
|
|
@@ -100,9 +108,11 @@ export async function planGenerateCommand({ task, config, logger, json, context
|
|
|
100
108
|
|
|
101
109
|
const planId = await savePlan(projectDir, plan);
|
|
102
110
|
|
|
111
|
+
runLog.logText(`[planner] finished — hus=${plan.hus.length} plan=${planId}`);
|
|
112
|
+
|
|
103
113
|
if (json) {
|
|
104
114
|
console.log(JSON.stringify(plan, null, 2));
|
|
105
|
-
return;
|
|
115
|
+
return { ok: true };
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
if (parsed?.approach) {
|
|
@@ -116,6 +126,7 @@ export async function planGenerateCommand({ task, config, logger, json, context
|
|
|
116
126
|
console.log(`Review: kj plan show ${planId}`);
|
|
117
127
|
console.log(`Approve: kj plan ready ${planId}`);
|
|
118
128
|
console.log(`Execute: kj run --plan ${planId} "${task.slice(0, 40)}..."`);
|
|
129
|
+
return { ok: true };
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
/**
|
|
@@ -20,21 +20,26 @@ function buildResearchPrompt(task) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export async function researcherCommand({ task, config, logger }) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const { withCliRunLog } = await import("../utils/cli-run-log.js");
|
|
24
|
+
return withCliRunLog("researcher", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
25
|
+
const researcherRole = resolveRole(config, "researcher");
|
|
26
|
+
await assertAgentsAvailable([researcherRole.provider]);
|
|
27
|
+
logger.info(`Researcher (${researcherRole.provider}) starting...`);
|
|
28
|
+
runLog.logText(`[researcher] provider=${researcherRole.provider}`);
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
const agent = createAgent(researcherRole.provider, config, logger);
|
|
31
|
+
const prompt = buildResearchPrompt(task);
|
|
32
|
+
const onOutput = ({ line }) => process.stdout.write(`${line}\n`);
|
|
33
|
+
const result = await agent.runTask({ prompt, onOutput, role: "researcher" });
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
if (!result.ok) {
|
|
36
|
+
throw new Error(result.error || result.output || "Researcher failed");
|
|
37
|
+
}
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
if (result.output) {
|
|
40
|
+
console.log(result.output);
|
|
41
|
+
}
|
|
42
|
+
logger.info("Researcher completed.");
|
|
43
|
+
return { ok: true };
|
|
44
|
+
});
|
|
40
45
|
}
|
package/src/commands/review.js
CHANGED
|
@@ -4,12 +4,15 @@ import { computeBaseRef, generateDiff } from "../review/diff-generator.js";
|
|
|
4
4
|
import { buildReviewerPrompt } from "../prompts/reviewer.js";
|
|
5
5
|
import { resolveRole } from "../config.js";
|
|
6
6
|
import { resolveReviewProfile } from "../review/profiles.js";
|
|
7
|
+
import { withCliRunLog } from "../utils/cli-run-log.js";
|
|
7
8
|
|
|
8
9
|
export async function reviewCommand({ task, config, logger, baseRef }) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
return withCliRunLog("review", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
11
|
+
const reviewerRole = resolveRole(config, "reviewer");
|
|
12
|
+
await assertAgentsAvailable([reviewerRole.provider]);
|
|
13
|
+
logger.info(`Reviewer (${reviewerRole.provider}) starting...`);
|
|
14
|
+
runLog.logText(`[reviewer] provider=${reviewerRole.provider} baseRef=${baseRef || "(auto)"}`);
|
|
15
|
+
const reviewer = createAgent(reviewerRole.provider, config, logger);
|
|
13
16
|
|
|
14
17
|
let diff;
|
|
15
18
|
if (config.ci?.enabled) {
|
|
@@ -75,4 +78,7 @@ export async function reviewCommand({ task, config, logger, baseRef }) {
|
|
|
75
78
|
logger.warn(`CI dispatch failed (non-blocking): ${err.message}`);
|
|
76
79
|
}
|
|
77
80
|
}
|
|
81
|
+
runLog.logText(`[reviewer] finished (exit=${result.exitCode})`);
|
|
82
|
+
return { ok: true };
|
|
83
|
+
});
|
|
78
84
|
}
|
package/src/commands/run.js
CHANGED
|
@@ -3,6 +3,7 @@ import readline from "node:readline";
|
|
|
3
3
|
import { runFlow } from "../orchestrator.js";
|
|
4
4
|
import { assertAgentsAvailable } from "../agents/availability.js";
|
|
5
5
|
import { createActivityLog } from "../activity-log.js";
|
|
6
|
+
import { withCliRunLog } from "../utils/cli-run-log.js";
|
|
6
7
|
import { printHeader } from "../utils/display/header.js";
|
|
7
8
|
import { printEvent } from "../utils/display/event-handlers.js";
|
|
8
9
|
import { resolveRole } from "../config.js";
|
|
@@ -57,32 +58,41 @@ export async function runCommandHandler({ task, config, logger, flags }) {
|
|
|
57
58
|
// Quiet mode is the default; --verbose disables it
|
|
58
59
|
const quietMode = config.output?.quiet !== false;
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
return withCliRunLog("run", { projectDir: config?.projectDir, logger }, async ({ runLog, forwardProgress }) => {
|
|
62
|
+
runLog.logText(`[kj_run] task="${String(task).slice(0, 80)}..."`);
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
activityLog = createActivityLog(event.sessionId);
|
|
66
|
-
logger.onLog((entry) => activityLog.write(entry));
|
|
67
|
-
}
|
|
64
|
+
const emitter = new EventEmitter();
|
|
65
|
+
let activityLog = null;
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
// Mirror every progress event into .kj/run.log so kj-tail works identically
|
|
68
|
+
// whether the command came from the MCP server or the CLI (KJC-TSK-0327 follow-up).
|
|
69
|
+
forwardProgress(emitter);
|
|
70
|
+
|
|
71
|
+
emitter.on("progress", (event) => {
|
|
72
|
+
if (!activityLog && event.sessionId) {
|
|
73
|
+
activityLog = createActivityLog(event.sessionId);
|
|
74
|
+
logger.onLog((entry) => activityLog.write(entry));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (activityLog) {
|
|
78
|
+
activityLog.writeEvent(event);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!jsonMode) {
|
|
82
|
+
printEvent(event, { quiet: quietMode });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
72
85
|
|
|
73
86
|
if (!jsonMode) {
|
|
74
|
-
|
|
87
|
+
printHeader({ task: task, config });
|
|
75
88
|
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
if (!jsonMode) {
|
|
79
|
-
printHeader({ task: task, config });
|
|
80
|
-
}
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
const askQuestion = createCliAskQuestion();
|
|
91
|
+
const result = await runFlow({ task: task, config, logger, flags, emitter, askQuestion, pgTaskId: pgCardId || null, pgProject: pgProject || null });
|
|
84
92
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
if (jsonMode) {
|
|
94
|
+
console.log(JSON.stringify(result, null, 2));
|
|
95
|
+
}
|
|
96
|
+
return { ok: !result?.paused && result?.approved !== false };
|
|
97
|
+
});
|
|
88
98
|
}
|
package/src/commands/triage.js
CHANGED
|
@@ -34,30 +34,36 @@ function formatTriage(result) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export async function triageCommand({ task, config, logger, json }) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const { withCliRunLog } = await import("../utils/cli-run-log.js");
|
|
38
|
+
return withCliRunLog("triage", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
39
|
+
const triageRole = resolveRole(config, "triage");
|
|
40
|
+
await assertAgentsAvailable([triageRole.provider]);
|
|
41
|
+
logger.info(`Triage (${triageRole.provider}) starting...`);
|
|
42
|
+
runLog.logText(`[triage] provider=${triageRole.provider}`);
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
const agent = createAgent(triageRole.provider, config, logger);
|
|
45
|
+
const prompt = buildTriagePrompt({ task });
|
|
46
|
+
const onOutput = ({ line }) => process.stdout.write(`${line}\n`);
|
|
47
|
+
const result = await agent.runTask({ prompt, onOutput, role: "triage" });
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
if (!result.ok) {
|
|
50
|
+
throw new Error(result.error || result.output || "Triage failed");
|
|
51
|
+
}
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
const parsed = parseMaybeJsonString(result.output);
|
|
54
|
+
if (parsed?.level) runLog.logText(`[triage] level=${parsed.level} roles=${(parsed.roles || []).join(",")}`);
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
if (json) {
|
|
57
|
+
console.log(JSON.stringify(parsed || result.output, null, 2));
|
|
58
|
+
return { ok: true };
|
|
59
|
+
}
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
if (parsed?.level) {
|
|
62
|
+
console.log(formatTriage(parsed));
|
|
63
|
+
} else {
|
|
64
|
+
console.log(result.output);
|
|
65
|
+
}
|
|
66
|
+
logger.info("Triage completed.");
|
|
67
|
+
return { ok: true };
|
|
68
|
+
});
|
|
63
69
|
}
|
package/src/config.js
CHANGED
|
@@ -290,6 +290,20 @@ export async function loadConfig(projectDir) {
|
|
|
290
290
|
throw new Error(`Invalid Karajan config:\n${formatConfigIssues(validation.issues)}`);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
// Deprecation tracking — `sonarqube.enabled` was a footgun: users would
|
|
294
|
+
// set it to `false` in their global ~/.karajan/kj.config.yml and then
|
|
295
|
+
// wonder why Sonar didn't run on a code task. Since v2.7.4 Sonar is
|
|
296
|
+
// intrinsic to Karajan for code tasks; the field is ignored. We surface
|
|
297
|
+
// a warning at run start (see emitConfigDeprecations in flow-runner.js)
|
|
298
|
+
// so users notice and clean up their config. Don't throw — keep
|
|
299
|
+
// back-compat so existing scripts keep working.
|
|
300
|
+
const userSetSonarEnabled = (globalConfig?.sonarqube?.enabled !== undefined)
|
|
301
|
+
|| (projectConfig?.sonarqube?.enabled !== undefined);
|
|
302
|
+
if (userSetSonarEnabled) {
|
|
303
|
+
merged._deprecated = merged._deprecated || {};
|
|
304
|
+
merged._deprecated.sonarqubeEnabledKey = true;
|
|
305
|
+
}
|
|
306
|
+
|
|
293
307
|
return { config: merged, path: configPath, exists: globalExists, hasProjectConfig: !!projectConfig };
|
|
294
308
|
}
|
|
295
309
|
|
|
@@ -423,7 +437,16 @@ function applyOutputModeOverrides(out, flags) {
|
|
|
423
437
|
|
|
424
438
|
function applyMiscOverrides(out, flags) {
|
|
425
439
|
if (flags[AUTO_SIMPLIFY_FLAG] !== undefined) out.pipeline.auto_simplify = Boolean(flags[AUTO_SIMPLIFY_FLAG]);
|
|
426
|
-
|
|
440
|
+
// `--no-sonar` and `sonarqube.enabled: false` are deprecated since v2.7.4.
|
|
441
|
+
// Sonar is intrinsic to Karajan for code tasks (sw/refactor/add-tests) and
|
|
442
|
+
// skipped for non-code tasks (audit/doc/infra/analysis/no-code) by policy.
|
|
443
|
+
// The flag is still accepted to avoid breaking scripts; it now emits a
|
|
444
|
+
// warning at run start and is otherwise ignored. See preflight-checks.js
|
|
445
|
+
// and flow-runner.js for the actual gate (resolvedPolicies.sonar).
|
|
446
|
+
if (flags.noSonar || flags.sonar === false) {
|
|
447
|
+
out._deprecated = out._deprecated || {};
|
|
448
|
+
out._deprecated.noSonarFlag = true;
|
|
449
|
+
}
|
|
427
450
|
out.sonarcloud = out.sonarcloud || {};
|
|
428
451
|
if (flags.enableSonarcloud === true) out.sonarcloud.enabled = true;
|
|
429
452
|
if (flags.noSonarcloud === true || flags.sonarcloud === false) out.sonarcloud.enabled = false;
|
|
@@ -53,6 +53,18 @@ export async function runDirectRole({
|
|
|
53
53
|
await assertAgentsAvailable([role.provider]);
|
|
54
54
|
|
|
55
55
|
const projectDir = await resolveProjectDir(server, args.projectDir);
|
|
56
|
+
|
|
57
|
+
// Allow taskFile as alternative to inline task — works for every direct
|
|
58
|
+
// role (discover/triage/researcher/architect/audit/impeccable/...) without
|
|
59
|
+
// per-handler wiring. The mutation is in-place on the role's runInput /
|
|
60
|
+
// initContext since they are already built with `task: a.task`.
|
|
61
|
+
if ((!runInput?.task || runInput.task === undefined) && args.taskFile) {
|
|
62
|
+
const { readTaskFile } = await import("../utils/task-file.js");
|
|
63
|
+
const loaded = await readTaskFile(args.taskFile, { projectDir });
|
|
64
|
+
if (runInput) runInput.task = loaded;
|
|
65
|
+
if (initContext) initContext.task = loaded;
|
|
66
|
+
args.task = loaded;
|
|
67
|
+
}
|
|
56
68
|
const runLog = createRunLog(projectDir);
|
|
57
69
|
const startMsg = logStartMsg || `[kj_${effectiveStage}] started`;
|
|
58
70
|
runLog.logText(startMsg);
|
|
@@ -56,6 +56,8 @@ export async function handlePlanDirect(a, server, extra) {
|
|
|
56
56
|
await assertAgentsAvailable([plannerRole.provider]);
|
|
57
57
|
|
|
58
58
|
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
59
|
+
const { ensureTaskFromFile } = await import("../shared-helpers.js");
|
|
60
|
+
await ensureTaskFromFile(a, projectDir);
|
|
59
61
|
const runLog = createRunLog(projectDir);
|
|
60
62
|
const silenceTimeoutMs = Number(config?.session?.max_agent_silence_minutes) > 0
|
|
61
63
|
? Math.round(Number(config.session.max_agent_silence_minutes) * 60 * 1000)
|
|
@@ -210,6 +212,8 @@ export async function handleCodeDirect(a, server, extra) {
|
|
|
210
212
|
await assertAgentsAvailable([coderRole.provider]);
|
|
211
213
|
|
|
212
214
|
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
215
|
+
const { ensureTaskFromFile } = await import("../shared-helpers.js");
|
|
216
|
+
await ensureTaskFromFile(a, projectDir);
|
|
213
217
|
const runLog = createRunLog(projectDir);
|
|
214
218
|
runLog.logText(`[kj_code] started — provider=${coderRole.provider}`);
|
|
215
219
|
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
@@ -261,6 +265,8 @@ export async function handleReviewDirect(a, server, extra) {
|
|
|
261
265
|
await assertAgentsAvailable([reviewerRole.provider]);
|
|
262
266
|
|
|
263
267
|
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
268
|
+
const { ensureTaskFromFile } = await import("../shared-helpers.js");
|
|
269
|
+
await ensureTaskFromFile(a, projectDir);
|
|
264
270
|
const runLog = createRunLog(projectDir);
|
|
265
271
|
runLog.logText(`[kj_review] started — provider=${reviewerRole.provider}`);
|
|
266
272
|
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
@@ -267,8 +267,14 @@ export async function handleResume(a, server, extra) {
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
export async function handleRun(a, server, extra) {
|
|
270
|
+
const { ensureTaskFromFile, resolveProjectDir: _rpd } = await import("../shared-helpers.js");
|
|
271
|
+
try {
|
|
272
|
+
await ensureTaskFromFile(a, a.projectDir || (await _rpd(server, a.projectDir).catch(() => null)));
|
|
273
|
+
} catch (err) {
|
|
274
|
+
return failPayload(`taskFile read failed: ${err.message}`);
|
|
275
|
+
}
|
|
270
276
|
if (!a.task) {
|
|
271
|
-
return failPayload("Missing required field: task");
|
|
277
|
+
return failPayload("Missing required field: task (or pass taskFile with a .md path)");
|
|
272
278
|
}
|
|
273
279
|
if (a.taskType) {
|
|
274
280
|
const validTypes = new Set(["sw", "infra", "doc", "add-tests", "refactor"]);
|
|
@@ -21,6 +21,29 @@ export { classifyError } from "./error-classifiers.js";
|
|
|
21
21
|
* Resolve the user's project directory.
|
|
22
22
|
* Priority: 1) explicit projectDir param, 2) MCP roots, 3) error with instructions.
|
|
23
23
|
*/
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the `task` argument for MCP handlers. If the caller passed
|
|
26
|
+
* `a.taskFile` (instead of or in addition to `a.task`), read the file and
|
|
27
|
+
* set `a.task` to its trimmed content. When both are given, the explicit
|
|
28
|
+
* `a.task` wins (symmetric with CLI `resolveTaskInput`).
|
|
29
|
+
*
|
|
30
|
+
* Mutates `a` in place so downstream handlers keep reading `a.task`
|
|
31
|
+
* without any change. Returns `a` for convenience.
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} a
|
|
34
|
+
* @param {string} [projectDir] - used to resolve relative file paths
|
|
35
|
+
* @returns {Promise<Object>} - the same `a`, with `a.task` filled in
|
|
36
|
+
*/
|
|
37
|
+
export async function ensureTaskFromFile(a, projectDir) {
|
|
38
|
+
if (!a) return a;
|
|
39
|
+
const hasTask = typeof a.task === "string" && a.task.trim().length > 0;
|
|
40
|
+
const hasFile = typeof a.taskFile === "string" && a.taskFile.trim().length > 0;
|
|
41
|
+
if (hasTask || !hasFile) return a;
|
|
42
|
+
const { readTaskFile } = await import("../utils/task-file.js");
|
|
43
|
+
a.task = await readTaskFile(a.taskFile, { projectDir });
|
|
44
|
+
return a;
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
export async function resolveProjectDir(server, explicitProjectDir) {
|
|
25
48
|
if (explicitProjectDir) return explicitProjectDir;
|
|
26
49
|
|