karajan-code 1.2.2 → 1.3.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.
@@ -0,0 +1,141 @@
1
+ import { TesterRole } from "../roles/tester-role.js";
2
+ import { SecurityRole } from "../roles/security-role.js";
3
+ import { addCheckpoint, saveSession } from "../session-store.js";
4
+ import { emitProgress, makeEvent } from "../utils/events.js";
5
+ import { invokeSolomon } from "./solomon-escalation.js";
6
+
7
+ export async function runTesterStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget, iteration, task, diff, askQuestion }) {
8
+ logger.setContext({ iteration, stage: "tester" });
9
+ emitProgress(
10
+ emitter,
11
+ makeEvent("tester:start", { ...eventBase, stage: "tester" }, {
12
+ message: "Tester evaluating test quality"
13
+ })
14
+ );
15
+
16
+ const tester = new TesterRole({ config, logger, emitter });
17
+ await tester.init({ task, iteration });
18
+ const testerStart = Date.now();
19
+ const testerOutput = await tester.run({ task, diff });
20
+ trackBudget({
21
+ role: "tester",
22
+ provider: config?.roles?.tester?.provider || coderRole.provider,
23
+ model: config?.roles?.tester?.model || coderRole.model,
24
+ result: testerOutput,
25
+ duration_ms: Date.now() - testerStart
26
+ });
27
+
28
+ await addCheckpoint(session, { stage: "tester", iteration, ok: testerOutput.ok });
29
+
30
+ emitProgress(
31
+ emitter,
32
+ makeEvent("tester:end", { ...eventBase, stage: "tester" }, {
33
+ status: testerOutput.ok ? "ok" : "fail",
34
+ message: testerOutput.ok ? "Tester passed" : `Tester: ${testerOutput.summary}`
35
+ })
36
+ );
37
+
38
+ if (!testerOutput.ok) {
39
+ const maxTesterRetries = config.session?.max_tester_retries ?? 1;
40
+ session.tester_retry_count = (session.tester_retry_count || 0) + 1;
41
+ await saveSession(session);
42
+
43
+ if (session.tester_retry_count >= maxTesterRetries) {
44
+ const solomonResult = await invokeSolomon({
45
+ config, logger, emitter, eventBase, stage: "tester", askQuestion, session, iteration,
46
+ conflict: {
47
+ stage: "tester",
48
+ task,
49
+ diff,
50
+ iterationCount: session.tester_retry_count,
51
+ maxIterations: maxTesterRetries,
52
+ history: [{ agent: "tester", feedback: testerOutput.summary }]
53
+ }
54
+ });
55
+
56
+ if (solomonResult.action === "pause") {
57
+ return { action: "pause", result: { paused: true, sessionId: session.id, question: solomonResult.question, context: "tester_fail_fast" } };
58
+ }
59
+ if (solomonResult.action === "subtask") {
60
+ return { action: "pause", result: { paused: true, sessionId: session.id, subtask: solomonResult.subtask, context: "tester_subtask" } };
61
+ }
62
+ // Solomon approved — proceed to next stage
63
+ return { action: "ok" };
64
+ }
65
+
66
+ session.last_reviewer_feedback = `Tester feedback: ${testerOutput.summary}`;
67
+ await saveSession(session);
68
+ return { action: "continue" };
69
+ }
70
+
71
+ session.tester_retry_count = 0;
72
+ return { action: "ok", stageResult: { ok: true, summary: testerOutput.summary || "All tests passed" } };
73
+ }
74
+
75
+ export async function runSecurityStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget, iteration, task, diff, askQuestion }) {
76
+ logger.setContext({ iteration, stage: "security" });
77
+ emitProgress(
78
+ emitter,
79
+ makeEvent("security:start", { ...eventBase, stage: "security" }, {
80
+ message: "Security auditing code"
81
+ })
82
+ );
83
+
84
+ const security = new SecurityRole({ config, logger, emitter });
85
+ await security.init({ task, iteration });
86
+ const securityStart = Date.now();
87
+ const securityOutput = await security.run({ task, diff });
88
+ trackBudget({
89
+ role: "security",
90
+ provider: config?.roles?.security?.provider || coderRole.provider,
91
+ model: config?.roles?.security?.model || coderRole.model,
92
+ result: securityOutput,
93
+ duration_ms: Date.now() - securityStart
94
+ });
95
+
96
+ await addCheckpoint(session, { stage: "security", iteration, ok: securityOutput.ok });
97
+
98
+ emitProgress(
99
+ emitter,
100
+ makeEvent("security:end", { ...eventBase, stage: "security" }, {
101
+ status: securityOutput.ok ? "ok" : "fail",
102
+ message: securityOutput.ok ? "Security audit passed" : `Security: ${securityOutput.summary}`
103
+ })
104
+ );
105
+
106
+ if (!securityOutput.ok) {
107
+ const maxSecurityRetries = config.session?.max_security_retries ?? 1;
108
+ session.security_retry_count = (session.security_retry_count || 0) + 1;
109
+ await saveSession(session);
110
+
111
+ if (session.security_retry_count >= maxSecurityRetries) {
112
+ const solomonResult = await invokeSolomon({
113
+ config, logger, emitter, eventBase, stage: "security", askQuestion, session, iteration,
114
+ conflict: {
115
+ stage: "security",
116
+ task,
117
+ diff,
118
+ iterationCount: session.security_retry_count,
119
+ maxIterations: maxSecurityRetries,
120
+ history: [{ agent: "security", feedback: securityOutput.summary }]
121
+ }
122
+ });
123
+
124
+ if (solomonResult.action === "pause") {
125
+ return { action: "pause", result: { paused: true, sessionId: session.id, question: solomonResult.question, context: "security_fail_fast" } };
126
+ }
127
+ if (solomonResult.action === "subtask") {
128
+ return { action: "pause", result: { paused: true, sessionId: session.id, subtask: solomonResult.subtask, context: "security_subtask" } };
129
+ }
130
+ // Solomon approved — proceed
131
+ return { action: "ok" };
132
+ }
133
+
134
+ session.last_reviewer_feedback = `Security feedback: ${securityOutput.summary}`;
135
+ await saveSession(session);
136
+ return { action: "continue" };
137
+ }
138
+
139
+ session.security_retry_count = 0;
140
+ return { action: "ok", stageResult: { ok: true, summary: securityOutput.summary || "No vulnerabilities found" } };
141
+ }
@@ -0,0 +1,149 @@
1
+ import { TriageRole } from "../roles/triage-role.js";
2
+ import { ResearcherRole } from "../roles/researcher-role.js";
3
+ import { PlannerRole } from "../roles/planner-role.js";
4
+ import { createAgent } from "../agents/index.js";
5
+ import { addCheckpoint, markSessionStatus } from "../session-store.js";
6
+ import { emitProgress, makeEvent } from "../utils/events.js";
7
+ import { parsePlannerOutput } from "../prompts/planner.js";
8
+
9
+ export async function runTriageStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget }) {
10
+ logger.setContext({ iteration: 0, stage: "triage" });
11
+ emitProgress(
12
+ emitter,
13
+ makeEvent("triage:start", { ...eventBase, stage: "triage" }, {
14
+ message: "Triage classifying task complexity"
15
+ })
16
+ );
17
+
18
+ const triage = new TriageRole({ config, logger, emitter });
19
+ await triage.init({ task: session.task, sessionId: session.id, iteration: 0 });
20
+ const triageStart = Date.now();
21
+ const triageOutput = await triage.run({ task: session.task });
22
+ trackBudget({
23
+ role: "triage",
24
+ provider: config?.roles?.triage?.provider || coderRole.provider,
25
+ model: config?.roles?.triage?.model || coderRole.model,
26
+ result: triageOutput,
27
+ duration_ms: Date.now() - triageStart
28
+ });
29
+
30
+ await addCheckpoint(session, { stage: "triage", iteration: 0, ok: triageOutput.ok });
31
+
32
+ const recommendedRoles = new Set(triageOutput.result?.roles || []);
33
+ const roleOverrides = {};
34
+ if (triageOutput.ok) {
35
+ roleOverrides.plannerEnabled = recommendedRoles.has("planner");
36
+ roleOverrides.researcherEnabled = recommendedRoles.has("researcher");
37
+ roleOverrides.refactorerEnabled = recommendedRoles.has("refactorer");
38
+ roleOverrides.reviewerEnabled = recommendedRoles.has("reviewer");
39
+ roleOverrides.testerEnabled = recommendedRoles.has("tester");
40
+ roleOverrides.securityEnabled = recommendedRoles.has("security");
41
+ }
42
+
43
+ const stageResult = {
44
+ ok: triageOutput.ok,
45
+ level: triageOutput.result?.level || null,
46
+ roles: Array.from(recommendedRoles),
47
+ reasoning: triageOutput.result?.reasoning || null
48
+ };
49
+
50
+ emitProgress(
51
+ emitter,
52
+ makeEvent("triage:end", { ...eventBase, stage: "triage" }, {
53
+ status: triageOutput.ok ? "ok" : "fail",
54
+ message: triageOutput.ok ? "Triage completed" : `Triage failed: ${triageOutput.summary}`,
55
+ detail: stageResult
56
+ })
57
+ );
58
+
59
+ return { roleOverrides, stageResult };
60
+ }
61
+
62
+ export async function runResearcherStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget }) {
63
+ logger.setContext({ iteration: 0, stage: "researcher" });
64
+ emitProgress(
65
+ emitter,
66
+ makeEvent("researcher:start", { ...eventBase, stage: "researcher" }, {
67
+ message: "Researcher investigating codebase"
68
+ })
69
+ );
70
+
71
+ const researcher = new ResearcherRole({ config, logger, emitter });
72
+ await researcher.init({ task: session.task });
73
+ const researchStart = Date.now();
74
+ const researchOutput = await researcher.run({ task: session.task });
75
+ trackBudget({
76
+ role: "researcher",
77
+ provider: config?.roles?.researcher?.provider || coderRole.provider,
78
+ model: config?.roles?.researcher?.model || coderRole.model,
79
+ result: researchOutput,
80
+ duration_ms: Date.now() - researchStart
81
+ });
82
+
83
+ await addCheckpoint(session, { stage: "researcher", iteration: 0, ok: researchOutput.ok });
84
+
85
+ emitProgress(
86
+ emitter,
87
+ makeEvent("researcher:end", { ...eventBase, stage: "researcher" }, {
88
+ status: researchOutput.ok ? "ok" : "fail",
89
+ message: researchOutput.ok ? "Research completed" : `Research failed: ${researchOutput.summary}`
90
+ })
91
+ );
92
+
93
+ const stageResult = { ok: researchOutput.ok, summary: researchOutput.summary || null };
94
+ const researchContext = researchOutput.ok ? researchOutput.result : null;
95
+
96
+ return { researchContext, stageResult };
97
+ }
98
+
99
+ export async function runPlannerStage({ config, logger, emitter, eventBase, session, plannerRole, researchContext, trackBudget }) {
100
+ const task = session.task;
101
+ logger.setContext({ iteration: 0, stage: "planner" });
102
+ emitProgress(
103
+ emitter,
104
+ makeEvent("planner:start", { ...eventBase, stage: "planner" }, {
105
+ message: `Planner (${plannerRole.provider}) running`,
106
+ detail: { planner: plannerRole.provider }
107
+ })
108
+ );
109
+
110
+ const planRole = new PlannerRole({ config, logger, emitter, createAgentFn: createAgent });
111
+ planRole.context = { task, research: researchContext };
112
+ await planRole.init();
113
+ const plannerStart = Date.now();
114
+ const planResult = await planRole.execute(task);
115
+ trackBudget({ role: "planner", provider: plannerRole.provider, model: plannerRole.model, result: planResult.result, duration_ms: Date.now() - plannerStart });
116
+
117
+ if (!planResult.ok) {
118
+ await markSessionStatus(session, "failed");
119
+ const details = planResult.result?.error || planResult.summary || "unknown error";
120
+ emitProgress(
121
+ emitter,
122
+ makeEvent("planner:end", { ...eventBase, stage: "planner" }, {
123
+ status: "fail",
124
+ message: `Planner failed: ${details}`
125
+ })
126
+ );
127
+ throw new Error(`Planner failed: ${details}`);
128
+ }
129
+
130
+ const planOutput = planResult.result?.plan || "";
131
+ const plannedTask = planOutput ? `${task}\n\nExecution plan:\n${planOutput}` : task;
132
+ const parsedPlan = parsePlannerOutput(planOutput);
133
+ const stageResult = {
134
+ ok: true,
135
+ title: parsedPlan?.title || null,
136
+ approach: parsedPlan?.approach || null,
137
+ steps: parsedPlan?.steps || [],
138
+ completedSteps: []
139
+ };
140
+
141
+ emitProgress(
142
+ emitter,
143
+ makeEvent("planner:end", { ...eventBase, stage: "planner" }, {
144
+ message: "Planner completed"
145
+ })
146
+ );
147
+
148
+ return { plannedTask, stageResult };
149
+ }
@@ -0,0 +1,39 @@
1
+ import { ReviewerRole } from "../roles/reviewer-role.js";
2
+ import { createAgent } from "../agents/index.js";
3
+ import { addCheckpoint } from "../session-store.js";
4
+
5
+ export async function runReviewerWithFallback({ reviewerName, config, logger, emitter, reviewInput, session, iteration, onAttemptResult }) {
6
+ const fallbackReviewer = config.reviewer_options?.fallback_reviewer;
7
+ const retries = Math.max(0, Number(config.reviewer_options?.retries ?? 1));
8
+ const candidates = [reviewerName];
9
+ if (fallbackReviewer && fallbackReviewer !== reviewerName) {
10
+ candidates.push(fallbackReviewer);
11
+ }
12
+
13
+ const attempts = [];
14
+ for (const name of candidates) {
15
+ const reviewerConfig = { ...config, roles: { ...config.roles, reviewer: { ...config.roles?.reviewer, provider: name } } };
16
+ const role = new ReviewerRole({ config: reviewerConfig, logger, emitter, createAgentFn: createAgent });
17
+ await role.init();
18
+ for (let attempt = 1; attempt <= retries + 1; attempt += 1) {
19
+ const execResult = await role.execute(reviewInput);
20
+ if (onAttemptResult) {
21
+ await onAttemptResult({ reviewer: name, result: execResult.result });
22
+ }
23
+ attempts.push({ reviewer: name, attempt, ok: execResult.ok, result: execResult.result, execResult });
24
+ await addCheckpoint(session, {
25
+ stage: "reviewer-attempt",
26
+ iteration,
27
+ reviewer: name,
28
+ attempt,
29
+ ok: execResult.ok
30
+ });
31
+
32
+ if (execResult.ok) {
33
+ return { execResult, attempts };
34
+ }
35
+ }
36
+ }
37
+
38
+ return { execResult: null, attempts };
39
+ }
@@ -0,0 +1,84 @@
1
+ import { SolomonRole } from "../roles/solomon-role.js";
2
+ import { addCheckpoint, pauseSession } from "../session-store.js";
3
+ import { emitProgress, makeEvent } from "../utils/events.js";
4
+
5
+ export async function invokeSolomon({ config, logger, emitter, eventBase, stage, conflict, askQuestion, session, iteration }) {
6
+ const solomonEnabled = Boolean(config.pipeline?.solomon?.enabled);
7
+
8
+ if (!solomonEnabled) {
9
+ return escalateToHuman({ askQuestion, session, emitter, eventBase, stage, conflict, iteration });
10
+ }
11
+
12
+ emitProgress(
13
+ emitter,
14
+ makeEvent("solomon:start", { ...eventBase, stage: "solomon" }, {
15
+ message: `Solomon arbitrating ${stage} conflict`,
16
+ detail: { conflictStage: stage }
17
+ })
18
+ );
19
+
20
+ const solomon = new SolomonRole({ config, logger, emitter });
21
+ await solomon.init({ task: conflict.task || session.task, iteration });
22
+ const ruling = await solomon.run({ conflict });
23
+
24
+ emitProgress(
25
+ emitter,
26
+ makeEvent("solomon:end", { ...eventBase, stage: "solomon" }, {
27
+ message: `Solomon ruling: ${ruling.result?.ruling || "unknown"}`,
28
+ detail: ruling.result
29
+ })
30
+ );
31
+
32
+ await addCheckpoint(session, {
33
+ stage: "solomon",
34
+ iteration,
35
+ ruling: ruling.result?.ruling,
36
+ escalate: ruling.result?.escalate,
37
+ subtask: ruling.result?.subtask?.title || null
38
+ });
39
+
40
+ if (!ruling.ok) {
41
+ return escalateToHuman({
42
+ askQuestion, session, emitter, eventBase, stage, iteration,
43
+ conflict: { ...conflict, solomonReason: ruling.result?.escalate_reason }
44
+ });
45
+ }
46
+
47
+ const r = ruling.result?.ruling;
48
+ if (r === "approve" || r === "approve_with_conditions") {
49
+ return { action: "continue", conditions: ruling.result?.conditions || [], ruling };
50
+ }
51
+
52
+ if (r === "create_subtask") {
53
+ return { action: "subtask", subtask: ruling.result?.subtask, ruling };
54
+ }
55
+
56
+ return { action: "continue", conditions: [], ruling };
57
+ }
58
+
59
+ export async function escalateToHuman({ askQuestion, session, emitter, eventBase, stage, conflict, iteration }) {
60
+ const reason = conflict?.solomonReason || `${stage} conflict unresolved`;
61
+ const question = `${stage} conflict requires human intervention: ${reason}\nDetails: ${JSON.stringify(conflict?.history?.slice(-2) || [], null, 2)}\n\nHow should we proceed?`;
62
+
63
+ if (askQuestion) {
64
+ const answer = await askQuestion(question, { iteration, stage });
65
+ if (answer) {
66
+ return { action: "continue", humanGuidance: answer };
67
+ }
68
+ }
69
+
70
+ await pauseSession(session, {
71
+ question,
72
+ context: { iteration, stage, conflict }
73
+ });
74
+ emitProgress(
75
+ emitter,
76
+ makeEvent("question", { ...eventBase, stage }, {
77
+ status: "paused",
78
+ message: question,
79
+ detail: { question, sessionId: session.id }
80
+ })
81
+ );
82
+
83
+ return { action: "pause", question };
84
+ }