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
|
@@ -1,112 +1,64 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createAgent as defaultCreateAgent } from "../agents/index.js";
|
|
1
|
+
import { AgentRole } from "./agent-role.js";
|
|
3
2
|
import { buildHuReviewerPrompt, parseHuReviewerOutput } from "../prompts/hu-reviewer.js";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
* Resolve the provider for the hu-reviewer role, falling back to coder.
|
|
7
|
-
* @param {object} config
|
|
8
|
-
* @returns {string}
|
|
9
|
-
*/
|
|
10
|
-
function resolveProvider(config) {
|
|
11
|
-
return (
|
|
12
|
-
config?.roles?.["hu-reviewer"]?.provider ||
|
|
13
|
-
config?.roles?.hu_reviewer?.provider ||
|
|
14
|
-
config?.roles?.coder?.provider ||
|
|
15
|
-
"claude"
|
|
16
|
-
);
|
|
17
|
-
}
|
|
4
|
+
const EMPTY_BATCH = { total: 0, certified: 0, needs_rewrite: 0, needs_context: 0, consolidated_questions: "" };
|
|
18
5
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*/
|
|
24
|
-
function buildSummary(parsed) {
|
|
25
|
-
const { batch_summary: bs } = parsed;
|
|
26
|
-
const parts = [`${bs.total} HU(s) evaluated`];
|
|
27
|
-
if (bs.certified > 0) parts.push(`${bs.certified} certified`);
|
|
28
|
-
if (bs.needs_rewrite > 0) parts.push(`${bs.needs_rewrite} need rewrite`);
|
|
29
|
-
if (bs.needs_context > 0) parts.push(`${bs.needs_context} need context`);
|
|
30
|
-
return `HU Review complete: ${parts.join(", ")}`;
|
|
31
|
-
}
|
|
6
|
+
export class HuReviewerRole extends AgentRole {
|
|
7
|
+
constructor(opts) {
|
|
8
|
+
super({ ...opts, name: "hu-reviewer" });
|
|
9
|
+
}
|
|
32
10
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
11
|
+
resolveProvider() {
|
|
12
|
+
return (
|
|
13
|
+
this.config?.roles?.["hu-reviewer"]?.provider ||
|
|
14
|
+
this.config?.roles?.hu_reviewer?.provider ||
|
|
15
|
+
this.config?.roles?.coder?.provider ||
|
|
16
|
+
"claude"
|
|
17
|
+
);
|
|
40
18
|
}
|
|
41
19
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const context = input?.context || null;
|
|
50
|
-
const onOutput = input?.onOutput || null;
|
|
20
|
+
extractInput(input) {
|
|
21
|
+
return {
|
|
22
|
+
stories: input?.stories || [],
|
|
23
|
+
context: input?.context || null,
|
|
24
|
+
onOutput: input?.onOutput || null
|
|
25
|
+
};
|
|
26
|
+
}
|
|
51
27
|
|
|
52
|
-
|
|
53
|
-
const
|
|
28
|
+
async buildPrompt({ stories, context }) {
|
|
29
|
+
const prompt = buildHuReviewerPrompt({
|
|
30
|
+
stories, instructions: this.instructions, context,
|
|
31
|
+
productContext: this.config?.productContext || null,
|
|
32
|
+
domainContext: this.config?.domainContext || null
|
|
33
|
+
});
|
|
34
|
+
return { prompt };
|
|
35
|
+
}
|
|
54
36
|
|
|
55
|
-
|
|
56
|
-
const runArgs = { prompt, role: "hu-reviewer" };
|
|
57
|
-
if (onOutput) runArgs.onOutput = onOutput;
|
|
58
|
-
const result = await agent.runTask(runArgs);
|
|
37
|
+
parseOutput(raw) { return parseHuReviewerOutput(raw); }
|
|
59
38
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
39
|
+
buildSuccessResult(parsed, provider) {
|
|
40
|
+
return { evaluations: parsed.evaluations, batch_summary: parsed.batch_summary, provider };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
buildSummary(parsed) {
|
|
44
|
+
const bs = parsed.batch_summary;
|
|
45
|
+
const parts = [`${bs.total} HU(s) evaluated`];
|
|
46
|
+
if (bs.certified > 0) parts.push(`${bs.certified} certified`);
|
|
47
|
+
if (bs.needs_rewrite > 0) parts.push(`${bs.needs_rewrite} need rewrite`);
|
|
48
|
+
if (bs.needs_context > 0) parts.push(`${bs.needs_context} need context`);
|
|
49
|
+
return `HU Review complete: ${parts.join(", ")}`;
|
|
50
|
+
}
|
|
71
51
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
raw: result.output,
|
|
81
|
-
provider
|
|
82
|
-
},
|
|
83
|
-
summary: "HU Review complete (unstructured output)",
|
|
84
|
-
usage: result.usage
|
|
85
|
-
};
|
|
86
|
-
}
|
|
52
|
+
handleParseNull(agentResult, provider) {
|
|
53
|
+
return {
|
|
54
|
+
ok: true,
|
|
55
|
+
result: { evaluations: [], batch_summary: { ...EMPTY_BATCH }, raw: agentResult.output, provider },
|
|
56
|
+
summary: "HU Review complete (unstructured output)",
|
|
57
|
+
usage: agentResult.usage
|
|
58
|
+
};
|
|
59
|
+
}
|
|
87
60
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
result: {
|
|
91
|
-
evaluations: parsed.evaluations,
|
|
92
|
-
batch_summary: parsed.batch_summary,
|
|
93
|
-
provider
|
|
94
|
-
},
|
|
95
|
-
summary: buildSummary(parsed),
|
|
96
|
-
usage: result.usage
|
|
97
|
-
};
|
|
98
|
-
} catch { /* agent output is not structured JSON */
|
|
99
|
-
return {
|
|
100
|
-
ok: true,
|
|
101
|
-
result: {
|
|
102
|
-
evaluations: [],
|
|
103
|
-
batch_summary: { total: 0, certified: 0, needs_rewrite: 0, needs_context: 0, consolidated_questions: "" },
|
|
104
|
-
raw: result.output,
|
|
105
|
-
provider
|
|
106
|
-
},
|
|
107
|
-
summary: "HU Review complete (unstructured output)",
|
|
108
|
-
usage: result.usage
|
|
109
|
-
};
|
|
110
|
-
}
|
|
61
|
+
handleParseError(_err, agentResult, provider) {
|
|
62
|
+
return this.handleParseNull(agentResult, provider);
|
|
111
63
|
}
|
|
112
64
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createAgent as defaultCreateAgent } from "../agents/index.js";
|
|
1
|
+
import { AgentRole } from "./agent-role.js";
|
|
3
2
|
import { isDevToolsMcpAvailable, ensureWebPerfSkills } from "../webperf/devtools-detect.js";
|
|
3
|
+
import { extractFirstJson } from "../utils/json-extract.js";
|
|
4
4
|
|
|
5
5
|
const SUBAGENT_PREAMBLE = [
|
|
6
6
|
"IMPORTANT: You are running as a Karajan sub-agent.",
|
|
@@ -8,135 +8,78 @@ const SUBAGENT_PREAMBLE = [
|
|
|
8
8
|
"Do NOT use any MCP tools. Focus only on auditing frontend/UI code for design quality."
|
|
9
9
|
].join(" ");
|
|
10
10
|
|
|
11
|
-
function resolveProvider(config) {
|
|
12
|
-
return (
|
|
13
|
-
config?.roles?.impeccable?.provider ||
|
|
14
|
-
config?.roles?.coder?.provider ||
|
|
15
|
-
"claude"
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function buildPrompt({ task, diff, instructions }) {
|
|
20
|
-
const sections = [SUBAGENT_PREAMBLE];
|
|
21
|
-
|
|
22
|
-
if (instructions) {
|
|
23
|
-
sections.push(instructions);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
sections.push(
|
|
27
|
-
`## Task\n${task}`
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
if (diff) {
|
|
31
|
-
sections.push(`## Git diff to audit\n${diff}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return sections.join("\n\n");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function parseImpeccableOutput(raw) {
|
|
38
|
-
const text = raw?.trim() || "";
|
|
39
|
-
const jsonMatch = /\{[\s\S]*\}/.exec(text);
|
|
40
|
-
if (!jsonMatch) return null;
|
|
41
|
-
return JSON.parse(jsonMatch[0]);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
11
|
function buildSummary(parsed) {
|
|
45
12
|
const verdict = parsed.verdict || "APPROVED";
|
|
46
13
|
const found = parsed.issuesFound || 0;
|
|
47
14
|
const fixed = parsed.issuesFixed || 0;
|
|
48
|
-
|
|
49
|
-
if (verdict === "APPROVED" || found === 0) {
|
|
50
|
-
return `Verdict: APPROVED; No frontend design issues found`;
|
|
51
|
-
}
|
|
15
|
+
if (verdict === "APPROVED" || found === 0) return "Verdict: APPROVED; No frontend design issues found";
|
|
52
16
|
|
|
53
17
|
const cats = parsed.categories || {};
|
|
54
|
-
const parts = Object.entries(cats)
|
|
55
|
-
.filter(([, count]) => count > 0)
|
|
56
|
-
.map(([cat, count]) => `${count} ${cat}`);
|
|
57
|
-
|
|
18
|
+
const parts = Object.entries(cats).filter(([, count]) => count > 0).map(([cat, count]) => `${count} ${cat}`);
|
|
58
19
|
return `Verdict: ${verdict}; ${found} issue(s) found, ${fixed} fixed (${parts.join(", ")})`;
|
|
59
20
|
}
|
|
60
21
|
|
|
61
|
-
export class ImpeccableRole extends
|
|
62
|
-
constructor(
|
|
22
|
+
export class ImpeccableRole extends AgentRole {
|
|
23
|
+
constructor(opts) {
|
|
24
|
+
const mode = opts.mode || "audit";
|
|
63
25
|
const roleName = mode === "refactoring" ? "impeccable-design" : "impeccable";
|
|
64
|
-
super({ name: roleName
|
|
65
|
-
this._createAgent = createAgentFn || defaultCreateAgent;
|
|
26
|
+
super({ ...opts, name: roleName });
|
|
66
27
|
this.mode = mode;
|
|
67
28
|
}
|
|
68
29
|
|
|
30
|
+
resolveProvider() {
|
|
31
|
+
return this.config?.roles?.impeccable?.provider || this.config?.roles?.coder?.provider || "claude";
|
|
32
|
+
}
|
|
33
|
+
|
|
69
34
|
async execute(input) {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
35
|
+
const task = typeof input === "string" ? input : input?.task || this.context?.task || "";
|
|
36
|
+
const diff = typeof input === "object" ? input?.diff || null : null;
|
|
37
|
+
const projectDir = typeof input === "object" ? input?.projectDir || null : null;
|
|
73
38
|
|
|
74
39
|
// Auto-install WebPerf skills when DevTools MCP is configured
|
|
75
40
|
if (isDevToolsMcpAvailable(this.config)) {
|
|
76
41
|
const dir = projectDir || process.cwd();
|
|
77
42
|
try {
|
|
78
43
|
const webperfResult = await ensureWebPerfSkills(dir, this.logger);
|
|
79
|
-
if (webperfResult.installed.length > 0) {
|
|
80
|
-
this.logger?.info?.(`WebPerf skills installed: ${webperfResult.installed.join(", ")}`);
|
|
81
|
-
}
|
|
44
|
+
if (webperfResult.installed.length > 0) this.logger?.info?.(`WebPerf skills installed: ${webperfResult.installed.join(", ")}`);
|
|
82
45
|
} catch (err) {
|
|
83
46
|
this.logger?.warn?.(`WebPerf skill installation failed: ${err.message}`);
|
|
84
47
|
}
|
|
85
48
|
} else {
|
|
86
|
-
this.logger?.
|
|
49
|
+
this.logger?.debug?.("Chrome DevTools MCP not configured — WebPerf skills skipped");
|
|
87
50
|
}
|
|
88
51
|
|
|
89
|
-
const provider = resolveProvider(
|
|
90
|
-
const agent = this.
|
|
52
|
+
const provider = this.resolveProvider();
|
|
53
|
+
const agent = this.createAgentInstance(provider);
|
|
54
|
+
|
|
55
|
+
const sections = [SUBAGENT_PREAMBLE];
|
|
56
|
+
if (this.instructions) sections.push(this.instructions);
|
|
57
|
+
sections.push(`## Task\n${task}`);
|
|
58
|
+
if (diff) sections.push(`## Git diff to audit\n${diff}`);
|
|
59
|
+
if (isDevToolsMcpAvailable(this.config)) {
|
|
60
|
+
sections.push("\nNote: WebPerf domain skills are available. Use them for Core Web Vitals analysis if the audit involves frontend performance.");
|
|
61
|
+
}
|
|
91
62
|
|
|
92
|
-
const
|
|
93
|
-
? "\n\nNote: WebPerf domain skills are available. Use them for Core Web Vitals analysis if the audit involves frontend performance."
|
|
94
|
-
: "";
|
|
95
|
-
const prompt = buildPrompt({ task, diff, instructions: this.instructions }) + webperfNote;
|
|
96
|
-
const result = await agent.runTask({ prompt, role: "impeccable" });
|
|
63
|
+
const result = await agent.runTask({ prompt: sections.join("\n\n"), role: "impeccable" });
|
|
97
64
|
|
|
98
65
|
if (!result.ok) {
|
|
99
|
-
return {
|
|
100
|
-
ok: false,
|
|
101
|
-
result: {
|
|
102
|
-
error: result.error || result.output || "Impeccable audit failed",
|
|
103
|
-
provider
|
|
104
|
-
},
|
|
105
|
-
summary: `Impeccable audit failed: ${result.error || "unknown error"}`
|
|
106
|
-
};
|
|
66
|
+
return { ok: false, result: { error: result.error || result.output || "Impeccable audit failed", provider }, summary: `Impeccable audit failed: ${result.error || "unknown error"}` };
|
|
107
67
|
}
|
|
108
68
|
|
|
109
69
|
try {
|
|
110
|
-
const parsed =
|
|
70
|
+
const parsed = extractFirstJson(result.output);
|
|
111
71
|
if (!parsed) {
|
|
112
|
-
return {
|
|
113
|
-
ok: false,
|
|
114
|
-
result: { error: "Failed to parse impeccable output: no JSON found", provider },
|
|
115
|
-
summary: "Impeccable output parse error: no JSON found"
|
|
116
|
-
};
|
|
72
|
+
return { ok: false, result: { error: "Failed to parse impeccable output: no JSON found", provider }, summary: "Impeccable output parse error: no JSON found" };
|
|
117
73
|
}
|
|
118
74
|
|
|
119
75
|
const verdict = parsed.verdict || (parsed.issuesFound > 0 ? "IMPROVED" : "APPROVED");
|
|
120
|
-
const ok = verdict === "APPROVED" || verdict === "IMPROVED";
|
|
121
|
-
|
|
122
76
|
return {
|
|
123
|
-
ok,
|
|
124
|
-
result: {
|
|
125
|
-
verdict,
|
|
126
|
-
issuesFound: parsed.issuesFound || 0,
|
|
127
|
-
issuesFixed: parsed.issuesFixed || 0,
|
|
128
|
-
categories: parsed.categories || {},
|
|
129
|
-
changes: parsed.changes || [],
|
|
130
|
-
provider
|
|
131
|
-
},
|
|
77
|
+
ok: verdict === "APPROVED" || verdict === "IMPROVED",
|
|
78
|
+
result: { verdict, issuesFound: parsed.issuesFound || 0, issuesFixed: parsed.issuesFixed || 0, categories: parsed.categories || {}, changes: parsed.changes || [], provider },
|
|
132
79
|
summary: buildSummary({ ...parsed, verdict })
|
|
133
80
|
};
|
|
134
81
|
} catch (err) {
|
|
135
|
-
return {
|
|
136
|
-
ok: false,
|
|
137
|
-
result: { error: `Failed to parse impeccable output: ${err.message}`, provider },
|
|
138
|
-
summary: `Impeccable output parse error: ${err.message}`
|
|
139
|
-
};
|
|
82
|
+
return { ok: false, result: { error: `Failed to parse impeccable output: ${err.message}`, provider }, summary: `Impeccable output parse error: ${err.message}` };
|
|
140
83
|
}
|
|
141
84
|
}
|
|
142
85
|
}
|
package/src/roles/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { BaseRole, ROLE_EVENTS, resolveRoleMdPath, loadFirstExisting } from "./base-role.js";
|
|
2
|
+
export { AgentRole } from "./agent-role.js";
|
|
2
3
|
export { PlannerRole } from "./planner-role.js";
|
|
3
4
|
export { ReviewerRole } from "./reviewer-role.js";
|
|
4
5
|
export { RefactorerRole } from "./refactorer-role.js";
|
|
@@ -14,3 +15,4 @@ export { DiscoverRole } from "./discover-role.js";
|
|
|
14
15
|
export { ArchitectRole } from "./architect-role.js";
|
|
15
16
|
export { AuditRole } from "./audit-role.js";
|
|
16
17
|
export { HuReviewerRole } from "./hu-reviewer-role.js";
|
|
18
|
+
export { KarajanBrainRole } from "./karajan-brain-role.js";
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { AgentRole } from "./agent-role.js";
|
|
2
|
+
import { extractFirstJson } from "../utils/json-extract.js";
|
|
3
|
+
|
|
4
|
+
const BRAIN_PREAMBLE = [
|
|
5
|
+
"You are Karajan Brain, the central orchestrator of a multi-agent AI coding pipeline.",
|
|
6
|
+
"You receive the output of a role that just completed and decide what happens next.",
|
|
7
|
+
"Your decisions MUST be intelligent, context-aware, and token-efficient.",
|
|
8
|
+
"Do NOT execute the task yourself — you route, enrich, and control the flow."
|
|
9
|
+
].join(" ");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* KarajanBrainRole — AI-powered central orchestrator.
|
|
13
|
+
* Receives role outputs and decides: next role, enriched prompt, direct actions.
|
|
14
|
+
* Only consults Solomon when facing a genuine dilemma.
|
|
15
|
+
*/
|
|
16
|
+
export class KarajanBrainRole extends AgentRole {
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
super({ ...opts, name: "karajan-brain" });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
resolveProvider() {
|
|
22
|
+
return (
|
|
23
|
+
this.config?.roles?.["karajan-brain"]?.provider ||
|
|
24
|
+
this.config?.brain?.provider ||
|
|
25
|
+
this.config?.roles?.coder?.provider ||
|
|
26
|
+
"claude"
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
extractInput(input) {
|
|
31
|
+
return {
|
|
32
|
+
currentRole: input?.currentRole || null,
|
|
33
|
+
roleOutput: input?.roleOutput || null,
|
|
34
|
+
pipelineState: input?.pipelineState || {},
|
|
35
|
+
availableRoles: input?.availableRoles || [],
|
|
36
|
+
task: input?.task || this.context?.task || "",
|
|
37
|
+
onOutput: input?.onOutput || null
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async buildPrompt({ currentRole, roleOutput, pipelineState, availableRoles, task }) {
|
|
42
|
+
const sections = [BRAIN_PREAMBLE];
|
|
43
|
+
if (this.instructions) sections.push(this.instructions);
|
|
44
|
+
|
|
45
|
+
sections.push(
|
|
46
|
+
"## Current pipeline state",
|
|
47
|
+
`Task: ${task}`,
|
|
48
|
+
`Last role executed: ${currentRole || "none"}`,
|
|
49
|
+
`Available roles: ${availableRoles.join(", ") || "coder, reviewer, tester, security"}`,
|
|
50
|
+
`Iteration: ${pipelineState.iteration || 0}/${pipelineState.maxIterations || 5}`
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (pipelineState.filesChanged != null) {
|
|
54
|
+
sections.push(`Files changed so far: ${pipelineState.filesChanged}`);
|
|
55
|
+
}
|
|
56
|
+
if (pipelineState.budget) {
|
|
57
|
+
sections.push(`Budget used: $${pipelineState.budget.cost_usd?.toFixed(2) || 0}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (roleOutput) {
|
|
61
|
+
const outputStr = typeof roleOutput === "string" ? roleOutput : JSON.stringify(roleOutput, null, 2);
|
|
62
|
+
const truncated = outputStr.length > 3000 ? outputStr.slice(0, 3000) + "...[truncated]" : outputStr;
|
|
63
|
+
sections.push(`## Output from ${currentRole}`, truncated);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
sections.push(
|
|
67
|
+
"## Your decision",
|
|
68
|
+
"Return a single JSON object with your routing decision:",
|
|
69
|
+
"```json",
|
|
70
|
+
"{",
|
|
71
|
+
' "nextRole": "coder|reviewer|tester|security|solomon|done",',
|
|
72
|
+
' "enrichedPrompt": "string with specific instructions for the next role, or null to use default",',
|
|
73
|
+
' "directActions": [{"type": "run_command|create_file|update_gitignore", "params": {}}],',
|
|
74
|
+
' "reasoning": "why this decision",',
|
|
75
|
+
' "consultSolomon": false,',
|
|
76
|
+
' "dilemma": null',
|
|
77
|
+
"}",
|
|
78
|
+
"```",
|
|
79
|
+
"",
|
|
80
|
+
"Guidelines:",
|
|
81
|
+
"- If coder produced 0 file changes and there's reviewer feedback: enrich the prompt with concrete file paths and resend to coder",
|
|
82
|
+
"- If tests are failing but node_modules missing: directAction to run npm install",
|
|
83
|
+
'- If you cannot decide or face a genuine dilemma (security vs deadline, conflicting gates): set consultSolomon=true and describe the dilemma',
|
|
84
|
+
"- If the pipeline should end (approved, max iterations, unrecoverable): set nextRole=done"
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return { prompt: sections.join("\n\n") };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
parseOutput(raw) {
|
|
91
|
+
return extractFirstJson(raw);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
buildSuccessResult(parsed, provider) {
|
|
95
|
+
return {
|
|
96
|
+
nextRole: parsed.nextRole || "coder",
|
|
97
|
+
enrichedPrompt: parsed.enrichedPrompt || null,
|
|
98
|
+
directActions: Array.isArray(parsed.directActions) ? parsed.directActions : [],
|
|
99
|
+
reasoning: parsed.reasoning || "",
|
|
100
|
+
consultSolomon: Boolean(parsed.consultSolomon),
|
|
101
|
+
dilemma: parsed.dilemma || null,
|
|
102
|
+
provider
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
buildSummary(parsed) {
|
|
107
|
+
const next = parsed.nextRole || "unknown";
|
|
108
|
+
const actions = Array.isArray(parsed.directActions) ? parsed.directActions.length : 0;
|
|
109
|
+
const solomon = parsed.consultSolomon ? " (consulting Solomon)" : "";
|
|
110
|
+
return `Brain: next=${next}, ${actions} direct action(s)${solomon}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
handleParseNull(agentResult, provider) {
|
|
114
|
+
// Fallback: continue with default flow
|
|
115
|
+
return {
|
|
116
|
+
ok: true,
|
|
117
|
+
result: {
|
|
118
|
+
nextRole: "coder",
|
|
119
|
+
enrichedPrompt: null,
|
|
120
|
+
directActions: [],
|
|
121
|
+
reasoning: "Unstructured output — falling back to default flow",
|
|
122
|
+
consultSolomon: false,
|
|
123
|
+
dilemma: null,
|
|
124
|
+
provider
|
|
125
|
+
},
|
|
126
|
+
summary: "Brain: fallback to default flow (unstructured output)",
|
|
127
|
+
usage: agentResult.usage
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
handleParseError(_err, agentResult, provider) {
|
|
132
|
+
return this.handleParseNull(agentResult, provider);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -1,34 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createAgent as defaultCreateAgent } from "../agents/index.js";
|
|
3
|
-
|
|
4
|
-
function resolveProvider(config) {
|
|
5
|
-
return (
|
|
6
|
-
config?.roles?.planner?.provider ||
|
|
7
|
-
config?.roles?.coder?.provider ||
|
|
8
|
-
"claude"
|
|
9
|
-
);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function resolvePlannerSilenceTimeoutMs(config) {
|
|
13
|
-
const minutes = Number(config?.session?.max_agent_silence_minutes);
|
|
14
|
-
if (!Number.isFinite(minutes) || minutes <= 0) return null;
|
|
15
|
-
return Math.round(minutes * 60 * 1000);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function resolvePlannerRuntimeTimeoutMs(config) {
|
|
19
|
-
const minutes = Number(config?.session?.max_planner_minutes);
|
|
20
|
-
if (!Number.isFinite(minutes) || minutes <= 0) return null;
|
|
21
|
-
return Math.round(minutes * 60 * 1000);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function appendDecompositionSection(sections, triageDecomposition) {
|
|
25
|
-
if (!triageDecomposition?.length) return;
|
|
26
|
-
sections.push("## Triage decomposition recommendation", "The triage stage determined this task should be decomposed. Suggested subtasks:");
|
|
27
|
-
for (let i = 0; i < triageDecomposition.length; i++) {
|
|
28
|
-
sections.push(`${i + 1}. ${triageDecomposition[i]}`);
|
|
29
|
-
}
|
|
30
|
-
sections.push("", "Focus your plan on the FIRST subtask only. List the remaining subtasks as 'pending_subtasks' in your output for documentation.", "");
|
|
31
|
-
}
|
|
1
|
+
import { AgentRole } from "./agent-role.js";
|
|
32
2
|
|
|
33
3
|
const RESEARCH_FIELDS = [
|
|
34
4
|
{ key: "affected_files", label: "Affected files" },
|
|
@@ -38,13 +8,18 @@ const RESEARCH_FIELDS = [
|
|
|
38
8
|
{ key: "prior_decisions", label: "Prior decisions" }
|
|
39
9
|
];
|
|
40
10
|
|
|
11
|
+
function appendDecompositionSection(sections, triageDecomposition) {
|
|
12
|
+
if (!triageDecomposition?.length) return;
|
|
13
|
+
sections.push("## Triage decomposition recommendation", "The triage stage determined this task should be decomposed. Suggested subtasks:");
|
|
14
|
+
for (let i = 0; i < triageDecomposition.length; i++) sections.push(`${i + 1}. ${triageDecomposition[i]}`);
|
|
15
|
+
sections.push("", "Focus your plan on the FIRST subtask only. List the remaining subtasks as 'pending_subtasks' in your output for documentation.", "");
|
|
16
|
+
}
|
|
17
|
+
|
|
41
18
|
function appendResearchSection(sections, research) {
|
|
42
19
|
if (!research) return;
|
|
43
20
|
sections.push("## Research findings");
|
|
44
21
|
for (const { key, label } of RESEARCH_FIELDS) {
|
|
45
|
-
if (research[key]?.length) {
|
|
46
|
-
sections.push(`${label}: ${research[key].join(", ")}`);
|
|
47
|
-
}
|
|
22
|
+
if (research[key]?.length) sections.push(`${label}: ${research[key].join(", ")}`);
|
|
48
23
|
}
|
|
49
24
|
sections.push("");
|
|
50
25
|
}
|
|
@@ -63,61 +38,51 @@ function appendArchitectSection(sections, architectContext) {
|
|
|
63
38
|
sections.push("");
|
|
64
39
|
}
|
|
65
40
|
|
|
66
|
-
function
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
sections.push(instructions, "");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
sections.push(
|
|
74
|
-
"Create an implementation plan for this task.",
|
|
75
|
-
"Return concise numbered steps focused on execution order and risk.",
|
|
76
|
-
""
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
if (productContext) {
|
|
80
|
-
sections.push("## Product Context", productContext, "");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (domainContext) {
|
|
84
|
-
sections.push("## Domain Context", domainContext, "");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
appendDecompositionSection(sections, triageDecomposition);
|
|
88
|
-
appendArchitectSection(sections, architectContext);
|
|
89
|
-
appendResearchSection(sections, research);
|
|
90
|
-
|
|
91
|
-
sections.push("## Task", task);
|
|
41
|
+
function resolveSilenceTimeoutMs(config) {
|
|
42
|
+
const minutes = Number(config?.session?.max_agent_silence_minutes);
|
|
43
|
+
return Number.isFinite(minutes) && minutes > 0 ? Math.round(minutes * 60 * 1000) : null;
|
|
44
|
+
}
|
|
92
45
|
|
|
93
|
-
|
|
46
|
+
function resolveRuntimeTimeoutMs(config) {
|
|
47
|
+
const minutes = Number(config?.session?.max_planner_minutes);
|
|
48
|
+
return Number.isFinite(minutes) && minutes > 0 ? Math.round(minutes * 60 * 1000) : null;
|
|
94
49
|
}
|
|
95
50
|
|
|
96
|
-
export class PlannerRole extends
|
|
97
|
-
constructor(
|
|
98
|
-
super({ name: "planner"
|
|
99
|
-
this._createAgent = createAgentFn || defaultCreateAgent;
|
|
51
|
+
export class PlannerRole extends AgentRole {
|
|
52
|
+
constructor(opts) {
|
|
53
|
+
super({ ...opts, name: "planner" });
|
|
100
54
|
}
|
|
101
55
|
|
|
102
56
|
async execute(input) {
|
|
103
|
-
const { task, onOutput } = typeof input === "string"
|
|
57
|
+
const { task: rawTask, onOutput } = typeof input === "string"
|
|
104
58
|
? { task: input, onOutput: null }
|
|
105
59
|
: { task: input?.task || input || "", onOutput: input?.onOutput || null };
|
|
106
|
-
|
|
60
|
+
|
|
61
|
+
const task = rawTask || this.context?.task || "";
|
|
107
62
|
const research = this.context?.research || null;
|
|
108
63
|
const triageDecomposition = this.context?.triageDecomposition || null;
|
|
109
64
|
const architectContext = this.context?.architecture || null;
|
|
110
|
-
const provider = resolveProvider(this.config);
|
|
111
|
-
|
|
112
|
-
const agent = this._createAgent(provider, this.config, this.logger);
|
|
113
|
-
const prompt = buildPrompt({ task: taskStr, instructions: this.instructions, research, triageDecomposition, architectContext, productContext: this.config?.productContext || null, domainContext: this.config?.domainContext || null });
|
|
114
65
|
|
|
115
|
-
const
|
|
66
|
+
const sections = [];
|
|
67
|
+
if (this.instructions) sections.push(this.instructions, "");
|
|
68
|
+
sections.push("Create an implementation plan for this task.", "Return concise numbered steps focused on execution order and risk.", "");
|
|
69
|
+
if (this.config?.productContext) sections.push("## Product Context", this.config.productContext, "");
|
|
70
|
+
if (this.config?.domainContext) sections.push("## Domain Context", this.config.domainContext, "");
|
|
71
|
+
appendDecompositionSection(sections, triageDecomposition);
|
|
72
|
+
appendArchitectSection(sections, architectContext);
|
|
73
|
+
appendResearchSection(sections, research);
|
|
74
|
+
sections.push("## Task", task);
|
|
75
|
+
|
|
76
|
+
const provider = this.resolveProvider();
|
|
77
|
+
const agent = this.createAgentInstance(provider);
|
|
78
|
+
const runArgs = { prompt: sections.join("\n"), role: "planner" };
|
|
116
79
|
if (onOutput) runArgs.onOutput = onOutput;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
80
|
+
|
|
81
|
+
const silenceMs = resolveSilenceTimeoutMs(this.config);
|
|
82
|
+
if (silenceMs) runArgs.silenceTimeoutMs = silenceMs;
|
|
83
|
+
const timeoutMs = resolveRuntimeTimeoutMs(this.config);
|
|
120
84
|
if (timeoutMs) runArgs.timeoutMs = timeoutMs;
|
|
85
|
+
|
|
121
86
|
const result = await agent.runTask(runArgs);
|
|
122
87
|
|
|
123
88
|
if (!result.ok) {
|