karajan-code 2.7.3 → 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/package.json +1 -1
- package/src/checks/ports.js +18 -2
- package/src/checks/runner.js +26 -10
- package/src/checks/tokens.js +77 -24
- package/src/config.js +24 -1
- package/src/orchestrator/flow-runner.js +62 -4
- package/src/orchestrator/preflight-checks.js +15 -1
- package/src/session-store.js +6 -0
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
> **v2.7.
|
|
26
|
+
> **v2.7.4 released** — Three contract fixes: (1) **Sonar is now intrinsic** to Karajan for code tasks (`sw`/`refactor`/`add-tests`) — `sonarqube.enabled` config and `--no-sonar` flag are ignored with a deprecation warning; a code task without a quality gate is not a job Karajan calls complete, like TDD; Solomon still may skip an iteration at runtime via rules. (2) **Preflight no longer demands API keys Karajan doesn't use** — the bogus `ANTHROPIC_API_KEY not set` check was blocking every Claude Code MCP run; replaced with a real CLI availability check (`claude`, `codex`, `gemini` on PATH). (3) **Orchestrator no longer crashes** with `Cannot read properties of undefined (reading 'push')` on the Solomon init-error path. Adds two architectural invariants (`no-provider-apis`, `sonar-intrinsic`) to CI so the contracts can't silently drift back. New `docs/TESTS.md` test-suite guide with diagrams. See [CHANGELOG.md](./CHANGELOG.md) for full history and [MIGRATION-v2.md](./MIGRATION-v2.md) for v1→v2 breaking changes.
|
|
27
27
|
|
|
28
28
|
You describe what you want to build. Karajan orchestrates multiple AI agents to plan it, implement it, test it, review it with SonarQube, and iterate. No babysitting required.
|
|
29
29
|
|
|
@@ -297,9 +297,9 @@ Not nostalgia, not stubbornness. I've been using JavaScript since 1997, when Bre
|
|
|
297
297
|
| Tool | Why |
|
|
298
298
|
|------|-----|
|
|
299
299
|
| [**RTK**](https://github.com/rtk-ai/rtk) | Reduces token consumption by 60-90% on Bash command outputs |
|
|
300
|
-
| [**Planning Game MCP**](https://github.com/
|
|
301
|
-
| [**GitHub MCP**](https://github.com/
|
|
302
|
-
| [**Chrome DevTools MCP**](https://github.com/
|
|
300
|
+
| [**Planning Game MCP**](https://github.com/manufosela/planning-game-xp-mcp) | Agile project management (tasks, sprints, estimation), XP-native |
|
|
301
|
+
| [**GitHub MCP**](https://github.com/github/github-mcp-server) | Create PRs, manage issues directly from the agent |
|
|
302
|
+
| [**Chrome DevTools MCP**](https://github.com/ChromeDevTools/chrome-devtools-mcp) | Verify UI changes visually after frontend modifications |
|
|
303
303
|
|
|
304
304
|
## Contributing
|
|
305
305
|
|
package/package.json
CHANGED
package/src/checks/ports.js
CHANGED
|
@@ -38,7 +38,18 @@ export function createSonarPortCheck() {
|
|
|
38
38
|
name: "port:sonar",
|
|
39
39
|
label: "SonarQube port",
|
|
40
40
|
strategy: STRATEGY.MANUAL,
|
|
41
|
-
|
|
41
|
+
// Since v2.7.4 Sonar is intrinsic to Karajan for code tasks, so the
|
|
42
|
+
// port check almost always applies. The only legitimate skip is when
|
|
43
|
+
// the user runs their own external Sonar instance (`external: true`),
|
|
44
|
+
// in which case the port is managed outside Karajan. The legacy
|
|
45
|
+
// `sonarqube.enabled` field is ignored (deprecation warning emitted
|
|
46
|
+
// at config load time — see emitConfigDeprecations in flow-runner.js).
|
|
47
|
+
applies: (config) => {
|
|
48
|
+
if (config?.sonarqube?.external === true) {
|
|
49
|
+
return { applies: false, reason: "sonarqube.external is true — port managed outside Karajan" };
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
},
|
|
42
53
|
async detect({ config }) {
|
|
43
54
|
const port = extractPortFromHost(config?.sonarqube?.host, DEFAULT_SONAR_PORT);
|
|
44
55
|
const free = await isPortAvailable(port);
|
|
@@ -73,7 +84,12 @@ export function createHuBoardPortCheck() {
|
|
|
73
84
|
name: "port:hu-board",
|
|
74
85
|
label: "HU Board port",
|
|
75
86
|
strategy: STRATEGY.AUTO,
|
|
76
|
-
applies: (config) =>
|
|
87
|
+
applies: (config) => {
|
|
88
|
+
if (config?.hu_board?.enabled === false) {
|
|
89
|
+
return { applies: false, reason: "hu_board.enabled is false in kj.config.yml — HU Board dashboard skipped" };
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
},
|
|
77
93
|
describe: "Rebind HU Board to the next free port",
|
|
78
94
|
async detect({ config }) {
|
|
79
95
|
const port = config?.hu_board?.port || DEFAULT_HU_BOARD_PORT;
|
package/src/checks/runner.js
CHANGED
|
@@ -55,19 +55,35 @@ export async function runChecks(checks, ctx, options = {}) {
|
|
|
55
55
|
const overrides = {};
|
|
56
56
|
|
|
57
57
|
// Build applicable entries (skipped checks noted up front).
|
|
58
|
+
// A check.applies(config) may return:
|
|
59
|
+
// - true / false — boolean form (back-compat).
|
|
60
|
+
// - an object { applies: boolean, reason?: string } — when false, `reason`
|
|
61
|
+
// is shown verbatim so users see WHY (e.g. "sonarqube.enabled is false
|
|
62
|
+
// in kj.config.yml") instead of the opaque "Not applicable" message
|
|
63
|
+
// that confused users debugging their config.
|
|
58
64
|
for (const check of checks) {
|
|
59
|
-
if (typeof check.applies
|
|
60
|
-
entries.push({
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
68
|
-
} else {
|
|
65
|
+
if (typeof check.applies !== "function") {
|
|
66
|
+
entries.push({ check, status: null, detail: null, runMs: 0, detectResult: null, remediated: false });
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const verdict = check.applies(config);
|
|
70
|
+
const isObj = verdict && typeof verdict === "object" && "applies" in verdict;
|
|
71
|
+
const applies = isObj ? Boolean(verdict.applies) : Boolean(verdict);
|
|
72
|
+
if (applies) {
|
|
69
73
|
entries.push({ check, status: null, detail: null, runMs: 0, detectResult: null, remediated: false });
|
|
74
|
+
continue;
|
|
70
75
|
}
|
|
76
|
+
const reason = (isObj && typeof verdict.reason === "string" && verdict.reason.trim())
|
|
77
|
+
? verdict.reason.trim()
|
|
78
|
+
: "Not applicable for current configuration";
|
|
79
|
+
entries.push({
|
|
80
|
+
check,
|
|
81
|
+
status: STATUS.SKIPPED,
|
|
82
|
+
detail: reason,
|
|
83
|
+
runMs: 0,
|
|
84
|
+
detectResult: null,
|
|
85
|
+
remediated: false,
|
|
86
|
+
});
|
|
71
87
|
}
|
|
72
88
|
|
|
73
89
|
// Phase 1: detect in parallel with timeout.
|
package/src/checks/tokens.js
CHANGED
|
@@ -1,41 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Provider CLI availability checks (preflight).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* active role
|
|
4
|
+
* IMPORTANT — Karajan does NOT call provider APIs directly. There is no
|
|
5
|
+
* `@anthropic-ai/sdk`, no `openai`, no `@google/generative-ai` in
|
|
6
|
+
* package.json. Every active role spawns its agent CLI as a subprocess
|
|
7
|
+
* (`claude`, `codex`, `gemini`, `opencode`, `aider`). The CLI handles its
|
|
8
|
+
* own auth, typically via OAuth + on-disk token (Claude Code, Codex CLI,
|
|
9
|
+
* Gemini CLI). The user does NOT need to set ANTHROPIC_API_KEY etc. for
|
|
10
|
+
* Karajan to work.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Pre-v2.7.4 this module checked for ANTHROPIC_API_KEY / OPENAI_API_KEY /
|
|
13
|
+
* GEMINI_API_KEY env vars and FAILed preflight when missing — a false
|
|
14
|
+
* positive that blocked every user running Karajan as an MCP child of
|
|
15
|
+
* Claude Code (where `apiKeySource: "none"` is the norm). Replaced with a
|
|
16
|
+
* CLI-availability check that mirrors what the orchestrator actually does.
|
|
17
|
+
*
|
|
18
|
+
* GH_TOKEN is still checked when `git.auto_pr=true`, because that flow uses
|
|
19
|
+
* `git push` directly with the env var.
|
|
14
20
|
*/
|
|
15
21
|
|
|
16
22
|
import { checkBinary } from "../utils/agent-detect.js";
|
|
17
|
-
import { getRequiredProviderEnvs,
|
|
23
|
+
import { getRequiredProviderEnvs, normalizeProvider } from "../utils/provider-env.js";
|
|
18
24
|
import { STRATEGY } from "./types.js";
|
|
19
25
|
|
|
20
26
|
/**
|
|
21
|
-
*
|
|
27
|
+
* Map canonical provider name → the CLI binary Karajan actually spawns.
|
|
28
|
+
* Keep in sync with src/agents/index.js.
|
|
22
29
|
*/
|
|
23
|
-
|
|
30
|
+
const PROVIDER_TO_CLI = Object.freeze({
|
|
31
|
+
anthropic: "claude",
|
|
32
|
+
openai: "codex",
|
|
33
|
+
google: "gemini",
|
|
34
|
+
opencode: "opencode",
|
|
35
|
+
aider: "aider",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build a CLI-availability check for one provider.
|
|
40
|
+
*
|
|
41
|
+
* The check runs `<cli> --version`. If the binary is on PATH and exits 0,
|
|
42
|
+
* preflight passes — Karajan can spawn it. If the CLI is missing, preflight
|
|
43
|
+
* fails with the install URL we already track in PROVIDER_ENV.
|
|
44
|
+
*
|
|
45
|
+
* Note: we do NOT verify that the CLI is *authenticated*. Doing so would
|
|
46
|
+
* require provider-specific introspection (`claude doctor`, `codex login
|
|
47
|
+
* status`, ...), each with its own quirks and exit codes. If the CLI is
|
|
48
|
+
* present but not logged in, the agent will fail at the first request with
|
|
49
|
+
* a clear error — and the orchestrator surfaces that error already.
|
|
50
|
+
*/
|
|
51
|
+
function createProviderCliCheck(entry) {
|
|
52
|
+
const cli = PROVIDER_TO_CLI[entry.provider];
|
|
24
53
|
return {
|
|
25
|
-
name: `
|
|
26
|
-
label:
|
|
54
|
+
name: `cli:${entry.provider}`,
|
|
55
|
+
label: `${entry.provider} CLI (${cli})`,
|
|
27
56
|
strategy: STRATEGY.MANUAL,
|
|
28
57
|
async detect() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
58
|
+
if (!cli) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
severity: "warn",
|
|
62
|
+
detail: `Unknown CLI for provider ${entry.provider}`,
|
|
63
|
+
extra: { provider: entry.provider },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const result = await checkBinary(cli);
|
|
67
|
+
if (result.ok) {
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
severity: "info",
|
|
71
|
+
detail: `${cli} on PATH (${result.version || "version unknown"})`,
|
|
72
|
+
extra: { provider: entry.provider, cli, version: result.version, path: result.path },
|
|
73
|
+
};
|
|
32
74
|
}
|
|
33
75
|
return {
|
|
34
76
|
ok: false,
|
|
35
77
|
severity: entry.optional ? "warn" : "fail",
|
|
36
|
-
detail: `${
|
|
37
|
-
fix: `
|
|
38
|
-
extra: { provider: entry.provider,
|
|
78
|
+
detail: `${cli} not found on PATH`,
|
|
79
|
+
fix: `Install the ${cli} CLI: see ${entry.howToGetUrl}`,
|
|
80
|
+
extra: { provider: entry.provider, cli, howToGetUrl: entry.howToGetUrl },
|
|
39
81
|
};
|
|
40
82
|
},
|
|
41
83
|
};
|
|
@@ -43,6 +85,8 @@ function createProviderTokenCheck(entry) {
|
|
|
43
85
|
|
|
44
86
|
/**
|
|
45
87
|
* GH token check + auto-remediation via `gh auth login` when it's installed.
|
|
88
|
+
* Kept from the previous version: this one IS legitimate because Karajan
|
|
89
|
+
* uses `gh` (or raw `git push`) directly when auto_pr is on.
|
|
46
90
|
*/
|
|
47
91
|
function createGhTokenCheck() {
|
|
48
92
|
return {
|
|
@@ -82,15 +126,24 @@ function createGhTokenCheck() {
|
|
|
82
126
|
}
|
|
83
127
|
|
|
84
128
|
/**
|
|
85
|
-
* Build the list of token checks for the active config.
|
|
86
|
-
*
|
|
129
|
+
* Build the list of CLI/token checks for the active config.
|
|
130
|
+
* - One CLI availability check per active provider (claude / codex / gemini / ...).
|
|
131
|
+
* - One GH token check, conditional on git.auto_pr.
|
|
132
|
+
*
|
|
133
|
+
* Function name kept as `getTokenChecks` for backward compat with
|
|
134
|
+
* preflight-checks.js, even though the bulk of the checks no longer touch
|
|
135
|
+
* tokens at all. Tracked: rename to `getProviderChecks` in a follow-up
|
|
136
|
+
* once consumers are updated.
|
|
87
137
|
*
|
|
88
138
|
* @param {Object} config
|
|
89
139
|
* @returns {import("./types.js").Check[]}
|
|
90
140
|
*/
|
|
91
141
|
export function getTokenChecks(config) {
|
|
92
142
|
const required = getRequiredProviderEnvs(config);
|
|
93
|
-
const checks = required.map(
|
|
143
|
+
const checks = required.map(createProviderCliCheck);
|
|
94
144
|
checks.push(createGhTokenCheck());
|
|
95
145
|
return checks;
|
|
96
146
|
}
|
|
147
|
+
|
|
148
|
+
// Re-export for tests that need to introspect the mapping.
|
|
149
|
+
export { PROVIDER_TO_CLI, normalizeProvider };
|
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;
|
|
@@ -726,6 +726,12 @@ async function runPreLoopStages({ config, logger, emitter, eventBase, session, f
|
|
|
726
726
|
|
|
727
727
|
let updatedConfig = resolvePipelinePolicies({ flags, config, stageResults, emitter, eventBase, session, pipelineFlags });
|
|
728
728
|
|
|
729
|
+
// Deprecation warnings recorded at config load time. Done here, after
|
|
730
|
+
// policies are resolved, so the message lands in context: if the user's
|
|
731
|
+
// legacy `sonarqube.enabled: false` is being ignored, this is the right
|
|
732
|
+
// moment for them to see it.
|
|
733
|
+
emitConfigDeprecations(updatedConfig, logger, emitter, eventBase);
|
|
734
|
+
|
|
729
735
|
// --- Preflight environment checks ---
|
|
730
736
|
const preflightResult = await runPreflightChecks({
|
|
731
737
|
config: updatedConfig, logger, emitter, eventBase,
|
|
@@ -920,6 +926,46 @@ async function runPreLoopStages({ config, logger, emitter, eventBase, session, f
|
|
|
920
926
|
* Setting skills.addyosmani.enabled = false, or removing "addyosmani" from
|
|
921
927
|
* skills.sources, disables this step entirely (reverts to OpenSkills-only).
|
|
922
928
|
*/
|
|
929
|
+
/**
|
|
930
|
+
* Print one-time deprecation warnings for config keys / CLI flags that
|
|
931
|
+
* Karajan no longer honours. Called once per run, after policy resolution
|
|
932
|
+
* so the message lands in context (the user sees what's being ignored
|
|
933
|
+
* RIGHT before the preflight that would have been affected).
|
|
934
|
+
*
|
|
935
|
+
* Currently surfaces:
|
|
936
|
+
* - `sonarqube.enabled` set in user config → ignored since v2.7.4
|
|
937
|
+
* - `--no-sonar` CLI flag → ignored since v2.7.4
|
|
938
|
+
*/
|
|
939
|
+
function emitConfigDeprecations(config, logger, emitter, eventBase) {
|
|
940
|
+
const dep = config?._deprecated;
|
|
941
|
+
if (!dep) return;
|
|
942
|
+
|
|
943
|
+
if (dep.sonarqubeEnabledKey) {
|
|
944
|
+
const m =
|
|
945
|
+
"DEPRECATED: `sonarqube.enabled` in kj.config.yml is ignored since v2.7.4. " +
|
|
946
|
+
"Sonar is intrinsic to Karajan for code tasks (sw/refactor/add-tests) and " +
|
|
947
|
+
"skipped for non-code tasks (audit/doc/infra/analysis/no-code) by policy. " +
|
|
948
|
+
"Remove the key from your config to silence this warning.";
|
|
949
|
+
logger.warn(m);
|
|
950
|
+
emitProgress(emitter, makeEvent("config:deprecated", { ...eventBase, stage: "config" }, {
|
|
951
|
+
message: m,
|
|
952
|
+
detail: { key: "sonarqube.enabled", since: "v2.7.4" },
|
|
953
|
+
}));
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (dep.noSonarFlag) {
|
|
957
|
+
const m =
|
|
958
|
+
"DEPRECATED: `--no-sonar` flag is ignored since v2.7.4. Sonar runs for code " +
|
|
959
|
+
"tasks by policy. To skip Sonar on a one-off run, use a non-code task type " +
|
|
960
|
+
"(e.g. `--task-type doc`) or rely on Solomon's runtime override.";
|
|
961
|
+
logger.warn(m);
|
|
962
|
+
emitProgress(emitter, makeEvent("config:deprecated", { ...eventBase, stage: "config" }, {
|
|
963
|
+
message: m,
|
|
964
|
+
detail: { flag: "--no-sonar", since: "v2.7.4" },
|
|
965
|
+
}));
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
923
969
|
async function ensureAddyosmaniSkills({ task, config, logger, session, emitter, eventBase }) {
|
|
924
970
|
const skillsConfig = config?.skills || {};
|
|
925
971
|
const sources = Array.isArray(skillsConfig.sources) ? skillsConfig.sources : ["addyosmani", "openskills", "local"];
|
|
@@ -1173,9 +1219,17 @@ async function runQualityGateStages({ config, logger, emitter, eventBase, sessio
|
|
|
1173
1219
|
if (tddResult.action === "pause") return { action: "return", result: tddResult.result };
|
|
1174
1220
|
if (tddResult.action === "continue") return { action: "continue" };
|
|
1175
1221
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1222
|
+
// Sonar runs for code tasks per policy. Since v2.7.4 it is NOT
|
|
1223
|
+
// toggleable via config — that's intrinsic to Karajan. The taskType
|
|
1224
|
+
// policy (resolved_policies.sonar) is the single source of truth.
|
|
1225
|
+
// Solomon may skip a single iteration via rule alerts; that's a
|
|
1226
|
+
// runtime decision, not a config option.
|
|
1227
|
+
//
|
|
1228
|
+
// Test-only escape hatch: globalThis.__KJ_DISABLE_SONAR_STAGE forces
|
|
1229
|
+
// the sonar stage off so tests don't have to spin up Docker. Set in
|
|
1230
|
+
// tests/setup.js by default; production code never sees it.
|
|
1231
|
+
const sonarStageDisabledForTest = globalThis.__KJ_DISABLE_SONAR_STAGE === true;
|
|
1232
|
+
if (!sonarStageDisabledForTest && session.resolved_policies?.sonar !== false) {
|
|
1179
1233
|
const sonarResult = await runSonarStage({
|
|
1180
1234
|
config, logger, emitter, eventBase, session, trackBudget, iteration: i,
|
|
1181
1235
|
repeatDetector, budgetSummary, sonarState, askQuestion, task, brainCtx
|
|
@@ -1864,7 +1918,11 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
1864
1918
|
} catch (initError) {
|
|
1865
1919
|
// Pre-loop stage failure → Solomon decides
|
|
1866
1920
|
logger.warn(`Init/pre-loop error — escalating to Solomon: ${initError.message}`);
|
|
1867
|
-
|
|
1921
|
+
// tempSession needs `checkpoints` because invokeSolomon → addCheckpoint
|
|
1922
|
+
// pushes into it. Pre-v2.7.4 this lacked the array and crashed with
|
|
1923
|
+
// "Cannot read properties of undefined (reading 'push')" the moment
|
|
1924
|
+
// Solomon was consulted on a preflight failure.
|
|
1925
|
+
const tempSession = { id: "init-error", task, status: "failed", checkpoints: [] };
|
|
1868
1926
|
const solomonResult = await invokeSolomon({
|
|
1869
1927
|
config, logger, emitter, eventBase: { sessionId: "init-error", iteration: 0, stage: "init", startedAt: Date.now() },
|
|
1870
1928
|
stage: "init_error", askQuestion, session: tempSession, iteration: 0,
|
|
@@ -168,7 +168,21 @@ async function checkSecurityAgent(config) {
|
|
|
168
168
|
* @returns {{ ok: boolean, checks: object[], remediations: string[], configOverrides: object, warnings: string[], errors: object[] }}
|
|
169
169
|
*/
|
|
170
170
|
export async function runPreflightChecks({ config, logger, emitter, eventBase, resolvedPolicies, securityEnabled }) {
|
|
171
|
-
|
|
171
|
+
// Sonar is intrinsic to Karajan for code tasks (sw/refactor/add-tests).
|
|
172
|
+
// Since v2.7.4 it is NOT toggleable via `config.sonarqube.enabled` —
|
|
173
|
+
// that field is ignored (deprecation warning emitted at config load).
|
|
174
|
+
// The taskType policy (resolved_policies.sonar from DEFAULT_POLICIES in
|
|
175
|
+
// src/guards/policy-resolver.js) is the single source of truth: sw /
|
|
176
|
+
// refactor / add-tests run Sonar, audit / doc / infra / analysis /
|
|
177
|
+
// no-code skip it. Solomon may decide to skip a single iteration via
|
|
178
|
+
// rule alerts — that's a runtime decision, not a config option.
|
|
179
|
+
//
|
|
180
|
+
// Test-only escape hatch: globalThis.__KJ_DISABLE_SONAR_STAGE === true
|
|
181
|
+
// forces sonar OFF in preflight too, so tests that don't want sonar
|
|
182
|
+
// events in their expected sequence don't have to set up Docker mocks.
|
|
183
|
+
// Set in tests/setup.js by default; production code never sees it.
|
|
184
|
+
const sonarStageDisabledForTest = globalThis.__KJ_DISABLE_SONAR_STAGE === true;
|
|
185
|
+
const sonarEnabled = !sonarStageDisabledForTest && resolvedPolicies.sonar !== false;
|
|
172
186
|
const isExternalSonar = Boolean(config.sonarqube?.external);
|
|
173
187
|
const sonarHost = resolveSonarHost(config.sonarqube?.host);
|
|
174
188
|
|
package/src/session-store.js
CHANGED
|
@@ -44,6 +44,12 @@ export async function loadSession(sessionId) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export async function addCheckpoint(session, checkpoint) {
|
|
47
|
+
// Defensive: any caller that builds a stub session (e.g. the init-error
|
|
48
|
+
// path in flow-runner.js, where `tempSession = { id, task, status }` has
|
|
49
|
+
// no checkpoints array) would otherwise crash with
|
|
50
|
+
// "Cannot read properties of undefined (reading 'push')". Guard once here
|
|
51
|
+
// so the bug class is gone instead of fixing every call site.
|
|
52
|
+
if (!Array.isArray(session.checkpoints)) session.checkpoints = [];
|
|
47
53
|
session.checkpoints.push({ at: new Date().toISOString(), ...checkpoint });
|
|
48
54
|
await saveSession(session);
|
|
49
55
|
}
|