karajan-code 1.59.0 → 2.0.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 +2 -0
- package/package.json +1 -1
- package/scripts/install.js +18 -15
- package/src/agents/aider-agent.js +0 -7
- package/src/agents/claude-agent.js +34 -6
- package/src/agents/codex-agent.js +2 -6
- package/src/agents/gemini-agent.js +0 -4
- package/src/agents/opencode-agent.js +0 -4
- package/src/{becaria → ci}/dispatch.js +13 -13
- package/src/{becaria → ci}/pr-diff.js +1 -1
- package/src/cli.js +3 -2
- package/src/commands/agents.js +0 -1
- package/src/commands/doctor.js +22 -40
- package/src/commands/init.js +8 -8
- package/src/commands/report.js +0 -13
- package/src/commands/review.js +17 -17
- package/src/commands/run.js +1 -2
- package/src/commands/status.js +1 -7
- package/src/config.js +10 -52
- package/src/domains/domain-loader.js +85 -4
- package/src/git/automation.js +9 -9
- package/src/guards/output-guard.js +0 -17
- package/src/mcp/handlers/direct-handlers.js +1 -1
- package/src/mcp/handlers/run-handler.js +1 -2
- package/src/mcp/sovereignty-guard.js +2 -2
- package/src/mcp/tools.js +3 -2
- package/src/orchestrator/brain-coordinator.js +165 -0
- package/src/orchestrator/ci-integration.js +139 -0
- package/src/orchestrator/config-init.js +435 -0
- package/src/orchestrator/direct-actions.js +186 -0
- package/src/orchestrator/feedback-enrichment.js +140 -0
- package/src/orchestrator/feedback-queue.js +152 -0
- package/src/orchestrator/flow-control.js +159 -0
- package/src/orchestrator/post-loop-stages.js +20 -15
- package/src/orchestrator/preflight-checks.js +7 -3
- package/src/orchestrator/role-output-compressor.js +190 -0
- package/src/orchestrator/session-journal.js +344 -0
- package/src/orchestrator/solomon-escalation.js +38 -5
- package/src/orchestrator/solomon-rules.js +32 -33
- package/src/orchestrator/stages/reviewer-stage.js +16 -5
- package/src/orchestrator/stages/sonar-stage.js +24 -1
- package/src/orchestrator/verification-gate.js +146 -0
- package/src/orchestrator.js +435 -661
- package/src/planning-game/client.js +2 -2
- package/src/planning-game/pipeline-adapter.js +1 -1
- package/src/prompts/coder.js +17 -3
- package/src/prompts/reviewer.js +2 -2
- package/src/prompts/rtk-snippet.js +3 -5
- package/src/review/diff-generator.js +66 -17
- package/src/review/snapshot-diff.js +122 -0
- package/src/review/tdd-policy.js +4 -2
- package/src/roles/agent-role.js +135 -0
- package/src/roles/architect-role.js +61 -108
- package/src/roles/audit-role.js +25 -71
- package/src/roles/coder-role.js +32 -57
- package/src/roles/discover-role.js +30 -71
- package/src/roles/hu-reviewer-role.js +51 -99
- package/src/roles/impeccable-role.js +34 -91
- package/src/roles/index.js +2 -0
- package/src/roles/karajan-brain-role.js +134 -0
- package/src/roles/planner-role.js +40 -75
- package/src/roles/refactorer-role.js +22 -64
- package/src/roles/researcher-role.js +52 -118
- package/src/roles/reviewer-role.js +69 -126
- package/src/roles/security-role.js +38 -102
- package/src/roles/solomon-role.js +71 -15
- package/src/roles/tester-role.js +121 -95
- package/src/roles/triage-role.js +50 -111
- package/src/session-store.js +1 -1
- package/src/skills/skill-detector.js +11 -0
- package/src/sonar/config-resolver.js +20 -1
- package/src/sonar/credentials.js +17 -0
- package/src/sonar/project-key.js +1 -1
- package/src/sonar/scanner.js +1 -1
- package/src/utils/banner.js +66 -0
- package/src/utils/display.js +35 -17
- package/src/utils/injection-guard.js +23 -8
- package/src/utils/logger.js +6 -1
- package/src/utils/process.js +3 -4
- package/src/utils/role-assigner.js +131 -0
- package/src/utils/update-check.js +5 -1
- package/src/utils/welcome.js +3 -8
- package/src/webperf/devtools-detect.js +70 -23
- package/templates/roles/coder.md +7 -0
- package/templates/roles/karajan-brain.md +95 -0
- package/templates/roles/solomon.md +67 -106
- package/templates/workflows/automerge.yml +4 -4
- package/templates/workflows/houston-override.yml +4 -4
- package/templates/workflows/{becaria-gateway.yml → kj-ci-gateway.yml} +12 -12
- package/src/proxy/adapters/anthropic.js +0 -131
- package/src/proxy/adapters/gemini.js +0 -106
- package/src/proxy/adapters/index.js +0 -51
- package/src/proxy/adapters/openai.js +0 -71
- package/src/proxy/compression/ai-compressor.js +0 -209
- package/src/proxy/compression/dedup-cache.js +0 -100
- package/src/proxy/compression/deterministic/bash-build.js +0 -60
- package/src/proxy/compression/deterministic/bash-git.js +0 -101
- package/src/proxy/compression/deterministic/bash-infra.js +0 -64
- package/src/proxy/compression/deterministic/bash-misc.js +0 -53
- package/src/proxy/compression/deterministic/bash-pkg.js +0 -49
- package/src/proxy/compression/deterministic/bash-test.js +0 -61
- package/src/proxy/compression/deterministic/glob.js +0 -26
- package/src/proxy/compression/deterministic/grep.js +0 -53
- package/src/proxy/compression/deterministic/index.js +0 -58
- package/src/proxy/compression/deterministic/read.js +0 -22
- package/src/proxy/compression/deterministic/utils.js +0 -33
- package/src/proxy/compression/pipeline.js +0 -270
- package/src/proxy/middleware/prompt-injector.js +0 -165
- package/src/proxy/middleware/response-interceptor.js +0 -223
- package/src/proxy/progress-monitor.js +0 -98
- package/src/proxy/proxy-lifecycle.js +0 -198
- package/src/proxy/proxy-server.js +0 -239
- package/src/proxy/security.js +0 -45
- /package/src/{becaria → ci}/index.js +0 -0
- /package/src/{becaria → ci}/repo.js +0 -0
package/src/commands/review.js
CHANGED
|
@@ -7,24 +7,24 @@ import { resolveReviewProfile } from "../review/profiles.js";
|
|
|
7
7
|
|
|
8
8
|
export async function reviewCommand({ task, config, logger, baseRef }) {
|
|
9
9
|
const reviewerRole = resolveRole(config, "reviewer");
|
|
10
|
-
await assertAgentsAvailable([reviewerRole.provider
|
|
10
|
+
await assertAgentsAvailable([reviewerRole.provider]);
|
|
11
11
|
logger.info(`Reviewer (${reviewerRole.provider}) starting...`);
|
|
12
12
|
const reviewer = createAgent(reviewerRole.provider, config, logger);
|
|
13
13
|
|
|
14
14
|
let diff;
|
|
15
|
-
if (config.
|
|
16
|
-
//
|
|
17
|
-
const { detectRepo, detectPrNumber } = await import("../
|
|
18
|
-
const { getPrDiff } = await import("../
|
|
15
|
+
if (config.ci?.enabled) {
|
|
16
|
+
// CI mode: read diff from open PR
|
|
17
|
+
const { detectRepo, detectPrNumber } = await import("../ci/repo.js");
|
|
18
|
+
const { getPrDiff } = await import("../ci/pr-diff.js");
|
|
19
19
|
const repo = await detectRepo();
|
|
20
20
|
const prNumber = await detectPrNumber();
|
|
21
21
|
if (!prNumber) {
|
|
22
|
-
throw new Error("
|
|
22
|
+
throw new Error("CI enabled but no open PR found for current branch. Create a PR first or disable CI.");
|
|
23
23
|
}
|
|
24
|
-
logger.info(`
|
|
24
|
+
logger.info(`CI: reading PR diff #${prNumber}`);
|
|
25
25
|
diff = await getPrDiff(prNumber);
|
|
26
26
|
// Store for dispatch later
|
|
27
|
-
config.
|
|
27
|
+
config._ci_pr = { repo, prNumber };
|
|
28
28
|
} else {
|
|
29
29
|
const resolvedBase = await computeBaseRef({ baseBranch: config.base_branch, baseRef });
|
|
30
30
|
diff = await generateDiff({ baseRef: resolvedBase });
|
|
@@ -42,12 +42,12 @@ export async function reviewCommand({ task, config, logger, baseRef }) {
|
|
|
42
42
|
console.log(result.output);
|
|
43
43
|
logger.info(`Reviewer completed (exit ${result.exitCode})`);
|
|
44
44
|
|
|
45
|
-
//
|
|
46
|
-
if (config.
|
|
45
|
+
// CI: dispatch review result
|
|
46
|
+
if (config.ci?.enabled && config._ci_pr) {
|
|
47
47
|
try {
|
|
48
|
-
const { dispatchReview, dispatchComment } = await import("../
|
|
49
|
-
const { repo, prNumber } = config.
|
|
50
|
-
const bc = config.
|
|
48
|
+
const { dispatchReview, dispatchComment } = await import("../ci/dispatch.js");
|
|
49
|
+
const { repo, prNumber } = config._ci_pr;
|
|
50
|
+
const bc = config.ci;
|
|
51
51
|
|
|
52
52
|
// Try to parse structured review from output
|
|
53
53
|
let review;
|
|
@@ -61,18 +61,18 @@ export async function reviewCommand({ task, config, logger, baseRef }) {
|
|
|
61
61
|
await dispatchReview({
|
|
62
62
|
repo, prNumber, event,
|
|
63
63
|
body: review.summary || result.output.slice(0, 500),
|
|
64
|
-
agent: "Reviewer",
|
|
64
|
+
agent: "Reviewer", ciConfig: bc
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
await dispatchComment({
|
|
68
68
|
repo, prNumber, agent: "Reviewer",
|
|
69
69
|
body: `Standalone review: ${event}\n\n${review.summary || result.output.slice(0, 1000)}`,
|
|
70
|
-
|
|
70
|
+
ciConfig: bc
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
logger.info(`
|
|
73
|
+
logger.info(`CI: dispatched review for PR #${prNumber}`);
|
|
74
74
|
} catch (err) {
|
|
75
|
-
logger.warn(`
|
|
75
|
+
logger.warn(`CI dispatch failed (non-blocking): ${err.message}`);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
}
|
package/src/commands/run.js
CHANGED
|
@@ -35,8 +35,7 @@ export async function runCommandHandler({ task, config, logger, flags }) {
|
|
|
35
35
|
} catch { /* non-blocking */ }
|
|
36
36
|
|
|
37
37
|
const requiredProviders = [
|
|
38
|
-
resolveRole(config, "coder").provider
|
|
39
|
-
config.reviewer_options?.fallback_reviewer
|
|
38
|
+
resolveRole(config, "coder").provider
|
|
40
39
|
];
|
|
41
40
|
if (config.pipeline?.reviewer?.enabled !== false) {
|
|
42
41
|
requiredProviders.push(resolveRole(config, "reviewer").provider);
|
package/src/commands/status.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { readRunLog } from "../utils/run-log.js";
|
|
6
6
|
import { loadMostRecentSession } from "../session-store.js";
|
|
7
7
|
import { buildDashboard } from "../utils/status-dashboard.js";
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Run the `kj status` command: print a human-readable dashboard to stdout.
|
|
@@ -33,10 +33,4 @@ export async function statusCommand({ lines = 50, projectDir } = {}) {
|
|
|
33
33
|
|
|
34
34
|
const dashboard = buildDashboard(session, logResult.lines || [], { stories });
|
|
35
35
|
console.log(dashboard);
|
|
36
|
-
|
|
37
|
-
const proxyStats = getProxyStats();
|
|
38
|
-
if (proxyStats) {
|
|
39
|
-
console.log("");
|
|
40
|
-
console.log(`Proxy: port ${proxyStats.port}, ${proxyStats.requests} requests, ${proxyStats.bytes_in} bytes in, ${proxyStats.bytes_out} bytes out`);
|
|
41
|
-
}
|
|
42
36
|
}
|
package/src/config.js
CHANGED
|
@@ -49,7 +49,7 @@ const DEFAULTS = {
|
|
|
49
49
|
model: null,
|
|
50
50
|
deterministic: true,
|
|
51
51
|
retries: 1,
|
|
52
|
-
fallback_reviewer:
|
|
52
|
+
fallback_reviewer: null
|
|
53
53
|
},
|
|
54
54
|
development: {
|
|
55
55
|
methodology: "tdd",
|
|
@@ -128,7 +128,7 @@ const DEFAULTS = {
|
|
|
128
128
|
policies: {},
|
|
129
129
|
serena: { enabled: false },
|
|
130
130
|
planning_game: { enabled: false, project_id: null, codeveloper: null },
|
|
131
|
-
|
|
131
|
+
ci: { enabled: false, review_event: "kj-review", comment_event: "kj-comment", comment_prefix: true },
|
|
132
132
|
git: { auto_commit: false, auto_push: false, auto_pr: false, auto_rebase: true, branch_prefix: "feat/" },
|
|
133
133
|
output: { report_dir: "./.reviews", log_level: "info", quiet: true },
|
|
134
134
|
budget: {
|
|
@@ -159,50 +159,12 @@ const DEFAULTS = {
|
|
|
159
159
|
failFast: {
|
|
160
160
|
repeatThreshold: 2
|
|
161
161
|
},
|
|
162
|
-
retry: {
|
|
163
|
-
max_attempts: 3,
|
|
164
|
-
initial_backoff_ms: 1000,
|
|
165
|
-
max_backoff_ms: 30000,
|
|
166
|
-
backoff_multiplier: 2,
|
|
167
|
-
jitter_factor: 0.1
|
|
168
|
-
},
|
|
169
162
|
webperf: {
|
|
170
163
|
enabled: true,
|
|
171
164
|
devtools_mcp: false,
|
|
172
165
|
thresholds: { lcp: 2500, cls: 0.1, inp: 200 }
|
|
173
166
|
},
|
|
174
167
|
telemetry: true,
|
|
175
|
-
proxy: {
|
|
176
|
-
enabled: true,
|
|
177
|
-
port: "auto",
|
|
178
|
-
compression: {
|
|
179
|
-
enabled: true,
|
|
180
|
-
ai_compression: false,
|
|
181
|
-
ai_model: "haiku",
|
|
182
|
-
ai_provider: "anthropic",
|
|
183
|
-
layers: {
|
|
184
|
-
git: true,
|
|
185
|
-
tests: true,
|
|
186
|
-
build: true,
|
|
187
|
-
infra: true,
|
|
188
|
-
packages: true,
|
|
189
|
-
read_dedup: true,
|
|
190
|
-
glob_truncate: true,
|
|
191
|
-
grep_collapse: true,
|
|
192
|
-
},
|
|
193
|
-
pressure_thresholds: {
|
|
194
|
-
low: 0.5,
|
|
195
|
-
medium: 0.8,
|
|
196
|
-
high: 0.9,
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
cache: {
|
|
200
|
-
persist_to_disk: true,
|
|
201
|
-
flush_interval_ms: 5000,
|
|
202
|
-
},
|
|
203
|
-
inject_prompts: true,
|
|
204
|
-
monitor: true,
|
|
205
|
-
},
|
|
206
168
|
guards: {
|
|
207
169
|
output: {
|
|
208
170
|
enabled: true,
|
|
@@ -398,12 +360,12 @@ function applyMethodologyOverride(out, flags) {
|
|
|
398
360
|
out.development.require_test_changes = methodology === "tdd";
|
|
399
361
|
}
|
|
400
362
|
|
|
401
|
-
function
|
|
402
|
-
out.
|
|
403
|
-
if (flags.
|
|
404
|
-
out.
|
|
405
|
-
//
|
|
406
|
-
if (out.
|
|
363
|
+
function applyCiOverride(out, flags) {
|
|
364
|
+
out.ci = out.ci || { enabled: false };
|
|
365
|
+
if (flags.enableCi === undefined) return;
|
|
366
|
+
out.ci.enabled = Boolean(flags.enableCi);
|
|
367
|
+
// CI requires git automation (commit + push + PR)
|
|
368
|
+
if (out.ci.enabled) {
|
|
407
369
|
out.git.auto_commit = true;
|
|
408
370
|
out.git.auto_push = true;
|
|
409
371
|
out.git.auto_pr = true;
|
|
@@ -432,10 +394,6 @@ function applyMiscOverrides(out, flags) {
|
|
|
432
394
|
if (flags.pgTask) out.planning_game.enabled = true;
|
|
433
395
|
if (flags.pgProject) out.planning_game.project_id = flags.pgProject;
|
|
434
396
|
|
|
435
|
-
out.proxy = out.proxy || {};
|
|
436
|
-
if (flags.noProxy) out.proxy.enabled = false;
|
|
437
|
-
if (flags.proxyPort) out.proxy.port = flags.proxyPort;
|
|
438
|
-
|
|
439
397
|
out.model_selection = out.model_selection || { enabled: true, tiers: {}, role_overrides: {} };
|
|
440
398
|
if (flags.smartModels === true) out.model_selection.enabled = true;
|
|
441
399
|
if (flags.smartModels === false || flags.noSmartModels === true) out.model_selection.enabled = false;
|
|
@@ -461,7 +419,7 @@ export function applyRunOverrides(config, flags) {
|
|
|
461
419
|
applyPipelineOverrides(out, flags);
|
|
462
420
|
applyScalarAndBooleanOverrides(out, flags);
|
|
463
421
|
applyMethodologyOverride(out, flags);
|
|
464
|
-
|
|
422
|
+
applyCiOverride(out, flags);
|
|
465
423
|
applyMiscOverrides(out, flags);
|
|
466
424
|
applyOutputModeOverrides(out, flags);
|
|
467
425
|
|
|
@@ -479,7 +437,7 @@ const AGENT_MODEL_SIGNATURES = {
|
|
|
479
437
|
gemini: ["gemini", "flash-"]
|
|
480
438
|
};
|
|
481
439
|
|
|
482
|
-
|
|
440
|
+
function isModelCompatible(agent, model) {
|
|
483
441
|
if (!model || !agent) return true;
|
|
484
442
|
const lower = model.toLowerCase();
|
|
485
443
|
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Project-local domains override user-global domains by directory name.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
9
|
-
import { join } from "node:path";
|
|
8
|
+
import { readdir, readFile, mkdir, writeFile, stat } from "node:fs/promises";
|
|
9
|
+
import { join, isAbsolute } from "node:path";
|
|
10
10
|
import yaml from "js-yaml";
|
|
11
11
|
import { getKarajanHome } from "../utils/paths.js";
|
|
12
12
|
|
|
@@ -103,8 +103,52 @@ function parseSections(content) {
|
|
|
103
103
|
return sections;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Auto-detect project context files (README.md, CLAUDE.md) as lightweight domains.
|
|
108
|
+
* Used as fallback when no explicit .karajan/domains/ are configured.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} projectDir — absolute path to project root
|
|
111
|
+
* @returns {Promise<DomainFile[]>}
|
|
112
|
+
*/
|
|
113
|
+
export async function autoDetectDomains(projectDir) {
|
|
114
|
+
if (!projectDir) return [];
|
|
115
|
+
|
|
116
|
+
const candidates = ["README.md", "CLAUDE.md", "docs/README.md"];
|
|
117
|
+
const detected = [];
|
|
118
|
+
|
|
119
|
+
for (const rel of candidates) {
|
|
120
|
+
const filePath = join(projectDir, rel);
|
|
121
|
+
let content;
|
|
122
|
+
try {
|
|
123
|
+
content = await readFile(filePath, "utf-8");
|
|
124
|
+
} catch { continue; }
|
|
125
|
+
|
|
126
|
+
if (!content || content.trim().length < 20) continue;
|
|
127
|
+
|
|
128
|
+
// Truncate to first 4000 chars to avoid bloating context
|
|
129
|
+
const trimmed = content.length > 4000 ? content.slice(0, 4000) + "\n\n[...truncated]" : content;
|
|
130
|
+
|
|
131
|
+
detected.push({
|
|
132
|
+
name: `auto:${rel}`,
|
|
133
|
+
description: `Auto-detected from ${rel}`,
|
|
134
|
+
tags: [],
|
|
135
|
+
version: "0.0.0",
|
|
136
|
+
author: "",
|
|
137
|
+
visibility: "private",
|
|
138
|
+
sources: [{ type: "auto-detect", note: rel }],
|
|
139
|
+
content: trimmed,
|
|
140
|
+
sections: parseSections(trimmed),
|
|
141
|
+
filePath,
|
|
142
|
+
origin: "auto-detect"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return detected;
|
|
147
|
+
}
|
|
148
|
+
|
|
106
149
|
/**
|
|
107
150
|
* Load all domains from project-local and user-global directories.
|
|
151
|
+
* Falls back to auto-detecting README.md/CLAUDE.md when no explicit domains found.
|
|
108
152
|
* Project-local domains override user-global domains when directory names match.
|
|
109
153
|
*
|
|
110
154
|
* @param {string|null} projectDir — absolute path to project root (null = user-global only)
|
|
@@ -119,7 +163,6 @@ export async function loadDomains(projectDir) {
|
|
|
119
163
|
const projectDomains = projectDomainDir ? await scanDomainDir(projectDomainDir, "project") : [];
|
|
120
164
|
|
|
121
165
|
// Build a map keyed by directory name for merge.
|
|
122
|
-
// We track by directory name (not domain name) because the override is by directory.
|
|
123
166
|
const merged = new Map();
|
|
124
167
|
|
|
125
168
|
for (const { dirName, domain } of userDomains) {
|
|
@@ -129,7 +172,14 @@ export async function loadDomains(projectDir) {
|
|
|
129
172
|
merged.set(dirName, domain); // project overrides user
|
|
130
173
|
}
|
|
131
174
|
|
|
132
|
-
|
|
175
|
+
const explicit = Array.from(merged.values());
|
|
176
|
+
|
|
177
|
+
// Fallback: auto-detect README.md, CLAUDE.md when no explicit domains found
|
|
178
|
+
if (explicit.length === 0 && projectDir) {
|
|
179
|
+
return autoDetectDomains(projectDir);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return explicit;
|
|
133
183
|
}
|
|
134
184
|
|
|
135
185
|
/**
|
|
@@ -162,3 +212,34 @@ async function scanDomainDir(dir, origin) {
|
|
|
162
212
|
|
|
163
213
|
return results;
|
|
164
214
|
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Persist an inline domain (text or file path) to .karajan/domains/inline/DOMAIN.md.
|
|
218
|
+
* If domainInput is a path to an existing .md file, reads its content.
|
|
219
|
+
* Otherwise treats it as inline text.
|
|
220
|
+
*
|
|
221
|
+
* @param {string} domainInput — inline text or absolute path to a .md file
|
|
222
|
+
* @param {string} projectDir — project root
|
|
223
|
+
*/
|
|
224
|
+
export async function persistInlineDomain(domainInput, projectDir) {
|
|
225
|
+
if (!domainInput || !projectDir) return;
|
|
226
|
+
|
|
227
|
+
let content = domainInput;
|
|
228
|
+
|
|
229
|
+
// If it looks like a file path, try to read it
|
|
230
|
+
if (isAbsolute(domainInput) && domainInput.endsWith(".md")) {
|
|
231
|
+
try {
|
|
232
|
+
const s = await stat(domainInput);
|
|
233
|
+
if (s.isFile()) {
|
|
234
|
+
content = await readFile(domainInput, "utf-8");
|
|
235
|
+
}
|
|
236
|
+
} catch { /* treat as inline text */ }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const domainDir = join(projectDir, ".karajan", "domains", "inline");
|
|
240
|
+
await mkdir(domainDir, { recursive: true });
|
|
241
|
+
|
|
242
|
+
const domainFile = join(domainDir, DOMAIN_FILE);
|
|
243
|
+
const wrapped = `---\nname: inline-domain\ndescription: Domain knowledge provided via --domain parameter\ntags: []\nversion: "0.0.0"\n---\n\n${content}\n`;
|
|
244
|
+
await writeFile(domainFile, wrapped, "utf-8");
|
|
245
|
+
}
|
package/src/git/automation.js
CHANGED
|
@@ -110,7 +110,7 @@ export function buildPrBody({ task, stageResults }) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Create an early PR after the first coder iteration (
|
|
113
|
+
* Create an early PR after the first coder iteration (Karajan CI flow).
|
|
114
114
|
* Commits, pushes, and creates a PR before the reviewer runs.
|
|
115
115
|
* Returns { prNumber, prUrl, commits } or null if nothing to commit.
|
|
116
116
|
*/
|
|
@@ -125,10 +125,10 @@ export async function earlyPrCreation({ gitCtx, task, logger, session, stageResu
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const commits = commitResult.commit ? [commitResult.commit] : [];
|
|
128
|
-
await addCheckpoint(session, { stage: "
|
|
128
|
+
await addCheckpoint(session, { stage: "ci-commit", committed: true });
|
|
129
129
|
|
|
130
130
|
await pushBranch(gitCtx.branch);
|
|
131
|
-
await addCheckpoint(session, { stage: "
|
|
131
|
+
await addCheckpoint(session, { stage: "ci-push", branch: gitCtx.branch });
|
|
132
132
|
logger.info(`Pushed branch for early PR: ${gitCtx.branch}`);
|
|
133
133
|
|
|
134
134
|
const body = buildPrBody({ task, stageResults });
|
|
@@ -138,7 +138,7 @@ export async function earlyPrCreation({ gitCtx, task, logger, session, stageResu
|
|
|
138
138
|
title: commitMessageFromTask(task),
|
|
139
139
|
body
|
|
140
140
|
});
|
|
141
|
-
await addCheckpoint(session, { stage: "
|
|
141
|
+
await addCheckpoint(session, { stage: "ci-pr", branch: gitCtx.branch, pr: prUrl });
|
|
142
142
|
logger.info(`Early PR created: ${prUrl}`);
|
|
143
143
|
|
|
144
144
|
// Extract PR number from URL (e.g. https://github.com/owner/repo/pull/42)
|
|
@@ -147,7 +147,7 @@ export async function earlyPrCreation({ gitCtx, task, logger, session, stageResu
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
|
-
* Incremental push after each coder iteration (
|
|
150
|
+
* Incremental push after each coder iteration (Karajan CI flow).
|
|
151
151
|
* Commits and pushes without creating a new PR.
|
|
152
152
|
*/
|
|
153
153
|
export async function incrementalPush({ gitCtx, task, logger, session }) {
|
|
@@ -161,10 +161,10 @@ export async function incrementalPush({ gitCtx, task, logger, session }) {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
const commits = commitResult.commit ? [commitResult.commit] : [];
|
|
164
|
-
await addCheckpoint(session, { stage: "
|
|
164
|
+
await addCheckpoint(session, { stage: "ci-incremental-commit", committed: true });
|
|
165
165
|
|
|
166
166
|
await pushBranch(gitCtx.branch);
|
|
167
|
-
await addCheckpoint(session, { stage: "
|
|
167
|
+
await addCheckpoint(session, { stage: "ci-incremental-push", branch: gitCtx.branch });
|
|
168
168
|
logger.info(`Incremental push: ${gitCtx.branch}`);
|
|
169
169
|
|
|
170
170
|
return { commits };
|
|
@@ -202,7 +202,7 @@ export async function finalizeGitAutomation({ config, gitCtx, task, logger, sess
|
|
|
202
202
|
logger.info(`Pushed branch: ${gitCtx.branch}`);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
let prUrl = session.
|
|
205
|
+
let prUrl = session.ci_pr_url || null;
|
|
206
206
|
if (config.git.auto_pr && !prUrl) {
|
|
207
207
|
const body = buildPrBody({ task, stageResults });
|
|
208
208
|
prUrl = await createPullRequest({
|
|
@@ -214,7 +214,7 @@ export async function finalizeGitAutomation({ config, gitCtx, task, logger, sess
|
|
|
214
214
|
await addCheckpoint(session, { stage: "git-pr", branch: gitCtx.branch, pr: prUrl });
|
|
215
215
|
logger.info("Pull request created");
|
|
216
216
|
} else if (prUrl) {
|
|
217
|
-
logger.info(`PR already exists (
|
|
217
|
+
logger.info(`PR already exists (CI flow): ${prUrl}`);
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
return { committed, branch: gitCtx.branch, prUrl, pr: prUrl, commits };
|
|
@@ -148,21 +148,4 @@ export function scanDiff(diff, config = {}) {
|
|
|
148
148
|
return { pass: !hasCritical, violations };
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
/**
|
|
152
|
-
* Run output guard on the current git diff.
|
|
153
|
-
* This is the main entry point for the pipeline integration.
|
|
154
|
-
*/
|
|
155
|
-
export async function runOutputGuard(config = {}, baseBranch = "main") {
|
|
156
|
-
const diffResult = await runCommand("git", ["diff", `origin/${baseBranch}...HEAD`]);
|
|
157
|
-
if (diffResult.exitCode !== 0) {
|
|
158
|
-
// Fallback: diff against HEAD~1
|
|
159
|
-
const fallback = await runCommand("git", ["diff", "HEAD~1"]);
|
|
160
|
-
if (fallback.exitCode !== 0) {
|
|
161
|
-
return { pass: true, violations: [], error: "Could not generate diff" };
|
|
162
|
-
}
|
|
163
|
-
return scanDiff(fallback.stdout, config);
|
|
164
|
-
}
|
|
165
|
-
return scanDiff(diffResult.stdout, config);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
151
|
export { DESTRUCTIVE_PATTERNS, CREDENTIAL_PATTERNS, DEFAULT_PROTECTED_FILES };
|
|
@@ -258,7 +258,7 @@ export async function handleReviewDirect(a, server, extra) {
|
|
|
258
258
|
const logger = createLogger(config.output.log_level, "mcp");
|
|
259
259
|
|
|
260
260
|
const reviewerRole = resolveRole(config, "reviewer");
|
|
261
|
-
await assertAgentsAvailable([reviewerRole.provider
|
|
261
|
+
await assertAgentsAvailable([reviewerRole.provider]);
|
|
262
262
|
|
|
263
263
|
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
264
264
|
const runLog = createRunLog(projectDir);
|
|
@@ -98,8 +98,7 @@ const PIPELINE_PROVIDER_ROLES = [
|
|
|
98
98
|
|
|
99
99
|
function collectRequiredProviders(config) {
|
|
100
100
|
const providers = [
|
|
101
|
-
resolveRole(config, "coder").provider
|
|
102
|
-
config.reviewer_options?.fallback_reviewer
|
|
101
|
+
resolveRole(config, "coder").provider
|
|
103
102
|
];
|
|
104
103
|
if (config.pipeline?.reviewer?.enabled !== false) {
|
|
105
104
|
providers.push(resolveRole(config, "reviewer").provider);
|
|
@@ -17,14 +17,14 @@ const ALLOWED_PARAMS = new Set([
|
|
|
17
17
|
"enableTester", "enableSecurity", "enableImpeccable",
|
|
18
18
|
"enableDiscover", "enableArchitect",
|
|
19
19
|
"architectModel", "huFile",
|
|
20
|
-
"enableSerena", "
|
|
20
|
+
"enableSerena", "enableCi",
|
|
21
21
|
"reviewerFallback", "reviewerRetries",
|
|
22
22
|
"mode", "maxIterations", "maxIterationMinutes", "maxTotalMinutes",
|
|
23
23
|
"baseBranch", "baseRef", "methodology",
|
|
24
24
|
"autoCommit", "autoPush", "autoPr", "autoRebase", "branchPrefix",
|
|
25
25
|
"autoSimplify", "smartModels", "checkpointInterval",
|
|
26
26
|
"taskType", "quiet", "noSonar", "enableSonarcloud",
|
|
27
|
-
"kjHome", "sonarToken", "timeoutMs",
|
|
27
|
+
"kjHome", "sonarToken", "timeoutMs", "domain",
|
|
28
28
|
// Sovereign flags — validated below but still "known"
|
|
29
29
|
"enableTriage", "enableHuReviewer",
|
|
30
30
|
]);
|
package/src/mcp/tools.js
CHANGED
|
@@ -79,7 +79,7 @@ export const tools = [
|
|
|
79
79
|
architectModel: { type: "string" },
|
|
80
80
|
huFile: { type: "string", description: "Path to YAML file with HU stories to certify before coding" },
|
|
81
81
|
enableSerena: { type: "boolean" },
|
|
82
|
-
|
|
82
|
+
enableCi: { type: "boolean", description: "Enable Karajan CI (early PR + dispatch comments/reviews)" },
|
|
83
83
|
reviewerFallback: { type: "string" },
|
|
84
84
|
reviewerRetries: { type: "number" },
|
|
85
85
|
mode: { type: "string" },
|
|
@@ -104,7 +104,8 @@ export const tools = [
|
|
|
104
104
|
enableSonarcloud: { type: "boolean", description: "Enable SonarCloud scan (complementary to SonarQube)" },
|
|
105
105
|
kjHome: { type: "string" },
|
|
106
106
|
sonarToken: { type: "string" },
|
|
107
|
-
timeoutMs: { type: "number" }
|
|
107
|
+
timeoutMs: { type: "number" },
|
|
108
|
+
domain: { type: "string", description: "Domain knowledge: inline text describing the project domain, or absolute path to a .md file. Auto-saved to .karajan/domains/ for the curator." }
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
},
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Brain Coordinator: integrates Karajan Brain + 5 support modules into the pipeline.
|
|
2
|
+
// Replaces ad-hoc role-to-role communication with a central intelligence.
|
|
3
|
+
|
|
4
|
+
import * as queue from "./feedback-queue.js";
|
|
5
|
+
import * as enrich from "./feedback-enrichment.js";
|
|
6
|
+
import { verifyCoderOutput, VerificationTracker } from "./verification-gate.js";
|
|
7
|
+
import { executeActions } from "./direct-actions.js";
|
|
8
|
+
import { compressRoleOutput, measureCompression } from "./role-output-compressor.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} BrainContext
|
|
12
|
+
* @property {Object} feedbackQueue - structured feedback queue
|
|
13
|
+
* @property {VerificationTracker} verificationTracker - tracks coder changes
|
|
14
|
+
* @property {Object} compressionStats - token savings per role
|
|
15
|
+
* @property {boolean} enabled - whether brain coordination is active
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a new brain context for a pipeline session.
|
|
20
|
+
*/
|
|
21
|
+
export function createBrainContext({ enabled = false } = {}) {
|
|
22
|
+
return {
|
|
23
|
+
feedbackQueue: queue.createQueue(),
|
|
24
|
+
verificationTracker: new VerificationTracker(),
|
|
25
|
+
compressionStats: { totalSaved: 0, perRole: {} },
|
|
26
|
+
enabled
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if brain coordination is enabled for this config.
|
|
32
|
+
*/
|
|
33
|
+
export function isBrainEnabled(config) {
|
|
34
|
+
return Boolean(config?.brain?.enabled) || Boolean(config?.pipeline?.brain?.enabled);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* After a role completes, push its feedback to the queue and compress.
|
|
39
|
+
* Returns the compressed output for passing to next role.
|
|
40
|
+
*/
|
|
41
|
+
export function processRoleOutput(ctx, { roleName, output, iteration }) {
|
|
42
|
+
if (!ctx.enabled) return output;
|
|
43
|
+
|
|
44
|
+
// Compress for next-role context
|
|
45
|
+
const compressed = compressRoleOutput(roleName, output);
|
|
46
|
+
const stats = measureCompression(output, compressed);
|
|
47
|
+
ctx.compressionStats.totalSaved += stats.savedTokens;
|
|
48
|
+
ctx.compressionStats.perRole[roleName] = stats;
|
|
49
|
+
|
|
50
|
+
// Extract feedback entries from specific role outputs
|
|
51
|
+
const entries = extractFeedbackEntries(roleName, output, iteration);
|
|
52
|
+
if (entries.length > 0) {
|
|
53
|
+
queue.addEntries(ctx.feedbackQueue, entries);
|
|
54
|
+
enrich.enrichQueue(ctx.feedbackQueue);
|
|
55
|
+
queue.deduplicate(ctx.feedbackQueue);
|
|
56
|
+
queue.prioritize(ctx.feedbackQueue);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return compressed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extract structured feedback entries from a role's output.
|
|
64
|
+
*/
|
|
65
|
+
function extractFeedbackEntries(roleName, output, iteration) {
|
|
66
|
+
if (!output || typeof output !== "object") return [];
|
|
67
|
+
|
|
68
|
+
const entries = [];
|
|
69
|
+
if (roleName === "reviewer" && output.blocking_issues) {
|
|
70
|
+
for (const issue of output.blocking_issues) {
|
|
71
|
+
entries.push({
|
|
72
|
+
source: "reviewer",
|
|
73
|
+
severity: issue.severity || "medium",
|
|
74
|
+
description: issue.description || "",
|
|
75
|
+
file: issue.file || null,
|
|
76
|
+
line: issue.line || null,
|
|
77
|
+
suggestedFix: issue.suggested_fix || null,
|
|
78
|
+
id: issue.id || null,
|
|
79
|
+
iteration
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
} else if (roleName === "tester" && output.verdict === "fail") {
|
|
83
|
+
const missing = output.missing_scenarios || [];
|
|
84
|
+
for (const scenario of missing) {
|
|
85
|
+
entries.push({
|
|
86
|
+
source: "tester",
|
|
87
|
+
severity: "high",
|
|
88
|
+
category: "tests",
|
|
89
|
+
description: `Missing scenario: ${scenario}`,
|
|
90
|
+
iteration
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (output.coverage?.overall != null && output.coverage.overall < 80) {
|
|
94
|
+
entries.push({
|
|
95
|
+
source: "tester",
|
|
96
|
+
severity: "high",
|
|
97
|
+
category: "tests",
|
|
98
|
+
description: `Coverage below 80% (${output.coverage.overall}%)`,
|
|
99
|
+
iteration
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} else if (roleName === "security" && output.verdict === "fail") {
|
|
103
|
+
for (const vuln of output.vulnerabilities || []) {
|
|
104
|
+
entries.push({
|
|
105
|
+
source: "security",
|
|
106
|
+
severity: vuln.severity || "high",
|
|
107
|
+
category: "security",
|
|
108
|
+
description: vuln.description || "",
|
|
109
|
+
file: vuln.file || null,
|
|
110
|
+
line: vuln.line || null,
|
|
111
|
+
suggestedFix: vuln.fix_suggestion || null,
|
|
112
|
+
iteration
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return entries;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Before coder runs, format the feedback queue as an actionable prompt.
|
|
121
|
+
*/
|
|
122
|
+
export function buildCoderFeedbackPrompt(ctx) {
|
|
123
|
+
if (!ctx.enabled || ctx.feedbackQueue.entries.length === 0) return null;
|
|
124
|
+
return enrich.formatEnrichedForCoder(ctx.feedbackQueue.entries);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* After coder runs, verify changes were made. Returns verification result.
|
|
129
|
+
*/
|
|
130
|
+
export function verifyCoderRan(ctx, { baseRef, projectDir }) {
|
|
131
|
+
if (!ctx.enabled) return { passed: true, filesChanged: 0, reason: "brain disabled" };
|
|
132
|
+
const result = verifyCoderOutput({ baseRef, projectDir });
|
|
133
|
+
ctx.verificationTracker.record(result);
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Execute direct actions (npm install, gitignore updates, etc.).
|
|
139
|
+
*/
|
|
140
|
+
export async function runDirectActions(ctx, actions, { cwd } = {}) {
|
|
141
|
+
if (!ctx.enabled || !actions?.length) return [];
|
|
142
|
+
return executeActions(actions, { cwd });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Clear feedback queue after coder addresses it.
|
|
147
|
+
*/
|
|
148
|
+
export function clearFeedback(ctx) {
|
|
149
|
+
if (!ctx.enabled) return;
|
|
150
|
+
queue.clear(ctx.feedbackQueue);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get a summary of brain activity for the session.
|
|
155
|
+
*/
|
|
156
|
+
export function summarize(ctx) {
|
|
157
|
+
if (!ctx.enabled) return null;
|
|
158
|
+
return {
|
|
159
|
+
feedbackQueueSize: ctx.feedbackQueue.entries.length,
|
|
160
|
+
queueCategories: queue.countByCategory(ctx.feedbackQueue),
|
|
161
|
+
consecutiveVerificationFailures: ctx.verificationTracker.consecutiveFailures,
|
|
162
|
+
compressionSaved: ctx.compressionStats.totalSaved,
|
|
163
|
+
compressionPerRole: ctx.compressionStats.perRole
|
|
164
|
+
};
|
|
165
|
+
}
|