karajan-code 1.2.2 → 1.2.3
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 -2
- package/docs/README.es.md +2 -2
- package/package.json +1 -1
- package/src/agents/availability.js +3 -9
- package/src/agents/index.js +32 -11
- package/src/agents/model-registry.js +62 -0
- package/src/mcp/orphan-guard.js +21 -0
- package/src/mcp/server.js +4 -0
- package/src/orchestrator/iteration-stages.js +404 -0
- package/src/orchestrator/post-loop-stages.js +141 -0
- package/src/orchestrator/pre-loop-stages.js +149 -0
- package/src/orchestrator/reviewer-fallback.js +39 -0
- package/src/orchestrator/solomon-escalation.js +84 -0
- package/src/orchestrator.js +80 -883
- package/src/prompts/planner.js +51 -0
- package/src/repeat-detector.js +11 -0
- package/src/roles/coder-role.js +4 -1
- package/src/roles/planner-role.js +2 -2
- package/src/roles/refactorer-role.js +2 -0
- package/src/roles/reviewer-role.js +13 -6
- package/src/utils/budget.js +30 -0
- package/src/utils/pricing.js +3 -13
package/src/prompts/planner.js
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
+
export function parsePlannerOutput(output) {
|
|
2
|
+
const text = String(output || "").trim();
|
|
3
|
+
if (!text) return null;
|
|
4
|
+
|
|
5
|
+
const lines = text
|
|
6
|
+
.split(/\r?\n/)
|
|
7
|
+
.map((line) => line.trim())
|
|
8
|
+
.filter(Boolean);
|
|
9
|
+
|
|
10
|
+
let title = null;
|
|
11
|
+
let approach = null;
|
|
12
|
+
const steps = [];
|
|
13
|
+
|
|
14
|
+
for (const line of lines) {
|
|
15
|
+
if (!title) {
|
|
16
|
+
const titleMatch = line.match(/^title\s*:\s*(.+)$/i);
|
|
17
|
+
if (titleMatch) {
|
|
18
|
+
title = titleMatch[1].trim();
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!approach) {
|
|
24
|
+
const approachMatch = line.match(/^(approach|strategy)\s*:\s*(.+)$/i);
|
|
25
|
+
if (approachMatch) {
|
|
26
|
+
approach = approachMatch[2].trim();
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const numberedStep = line.match(/^\d+[\).:-]\s*(.+)$/);
|
|
32
|
+
if (numberedStep) {
|
|
33
|
+
steps.push(numberedStep[1].trim());
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const bulletStep = line.match(/^[-*]\s+(.+)$/);
|
|
38
|
+
if (bulletStep) {
|
|
39
|
+
steps.push(bulletStep[1].trim());
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!title) {
|
|
45
|
+
const firstFreeLine = lines.find((line) => !/^(approach|strategy)\s*:/i.test(line) && !/^\d+[\).:-]\s*/.test(line));
|
|
46
|
+
title = firstFreeLine || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { title, approach, steps };
|
|
50
|
+
}
|
|
51
|
+
|
|
1
52
|
export function buildPlannerPrompt({ task, context }) {
|
|
2
53
|
const parts = [
|
|
3
54
|
"You are an expert software architect. Create an implementation plan for the following task.",
|
package/src/repeat-detector.js
CHANGED
|
@@ -40,6 +40,17 @@ function parseThreshold(value) {
|
|
|
40
40
|
return DEFAULT_THRESHOLD;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export function getRepeatThreshold(config) {
|
|
44
|
+
const raw =
|
|
45
|
+
config?.failFast?.repeatThreshold ??
|
|
46
|
+
config?.session?.repeat_detection_threshold ??
|
|
47
|
+
config?.session?.fail_fast_repeats ??
|
|
48
|
+
2;
|
|
49
|
+
const value = Number(raw);
|
|
50
|
+
if (Number.isFinite(value) && value > 0) return value;
|
|
51
|
+
return 2;
|
|
52
|
+
}
|
|
53
|
+
|
|
43
54
|
export class RepeatDetector {
|
|
44
55
|
constructor({ threshold = DEFAULT_THRESHOLD } = {}) {
|
|
45
56
|
this.threshold = parseThreshold(threshold);
|
package/src/roles/coder-role.js
CHANGED
|
@@ -29,7 +29,8 @@ export class CoderRole extends BaseRole {
|
|
|
29
29
|
reviewerFeedback: reviewerFeedback || null,
|
|
30
30
|
sonarSummary: sonarSummary || null,
|
|
31
31
|
coderRules: this.instructions,
|
|
32
|
-
methodology: this.config?.development?.methodology || "tdd"
|
|
32
|
+
methodology: this.config?.development?.methodology || "tdd",
|
|
33
|
+
serenaEnabled: Boolean(this.config?.serena?.enabled)
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
const coderArgs = { prompt, role: "coder" };
|
|
@@ -41,6 +42,7 @@ export class CoderRole extends BaseRole {
|
|
|
41
42
|
return {
|
|
42
43
|
ok: false,
|
|
43
44
|
result: {
|
|
45
|
+
...result,
|
|
44
46
|
error: result.error || result.output || "Coder failed",
|
|
45
47
|
provider
|
|
46
48
|
},
|
|
@@ -51,6 +53,7 @@ export class CoderRole extends BaseRole {
|
|
|
51
53
|
return {
|
|
52
54
|
ok: true,
|
|
53
55
|
result: {
|
|
56
|
+
...result,
|
|
54
57
|
output: result.output || "",
|
|
55
58
|
provider
|
|
56
59
|
},
|
|
@@ -66,7 +66,7 @@ export class PlannerRole extends BaseRole {
|
|
|
66
66
|
if (!result.ok) {
|
|
67
67
|
return {
|
|
68
68
|
ok: false,
|
|
69
|
-
result: { error: result.error || result.output || "Planner agent failed", plan: null },
|
|
69
|
+
result: { ...result, error: result.error || result.output || "Planner agent failed", plan: null },
|
|
70
70
|
summary: `Planner failed: ${result.error || "unknown error"}`
|
|
71
71
|
};
|
|
72
72
|
}
|
|
@@ -74,7 +74,7 @@ export class PlannerRole extends BaseRole {
|
|
|
74
74
|
const plan = result.output?.trim() || "";
|
|
75
75
|
return {
|
|
76
76
|
ok: true,
|
|
77
|
-
result: { plan, provider },
|
|
77
|
+
result: { ...result, plan, provider },
|
|
78
78
|
summary: plan ? `Plan generated (${plan.split("\n").length} lines)` : "Empty plan generated"
|
|
79
79
|
};
|
|
80
80
|
}
|
|
@@ -47,6 +47,7 @@ export class RefactorerRole extends BaseRole {
|
|
|
47
47
|
return {
|
|
48
48
|
ok: false,
|
|
49
49
|
result: {
|
|
50
|
+
...result,
|
|
50
51
|
error: result.error || result.output || "Refactorer failed",
|
|
51
52
|
provider
|
|
52
53
|
},
|
|
@@ -57,6 +58,7 @@ export class RefactorerRole extends BaseRole {
|
|
|
57
58
|
return {
|
|
58
59
|
ok: true,
|
|
59
60
|
result: {
|
|
61
|
+
...result,
|
|
60
62
|
output: result.output?.trim() || "",
|
|
61
63
|
provider
|
|
62
64
|
},
|
|
@@ -101,29 +101,36 @@ export class ReviewerRole extends BaseRole {
|
|
|
101
101
|
|
|
102
102
|
try {
|
|
103
103
|
const parsed = parseReviewOutput(result.output);
|
|
104
|
-
const approved = Boolean(parsed.approved);
|
|
105
104
|
const blockingIssues = parsed.blocking_issues || [];
|
|
106
105
|
|
|
107
106
|
return {
|
|
108
107
|
ok: true,
|
|
109
108
|
result: {
|
|
110
|
-
|
|
109
|
+
...result,
|
|
110
|
+
approved: parsed.approved,
|
|
111
111
|
blocking_issues: blockingIssues,
|
|
112
112
|
non_blocking_suggestions: parsed.non_blocking_suggestions || [],
|
|
113
113
|
confidence: parsed.confidence ?? null,
|
|
114
114
|
raw_summary: parsed.summary || ""
|
|
115
115
|
},
|
|
116
|
-
summary: approved
|
|
116
|
+
summary: parsed.approved
|
|
117
117
|
? `Approved: ${parsed.summary || "no issues found"}`
|
|
118
118
|
: `Rejected: ${blockingIssues.length} blocking issue(s) — ${parsed.summary || ""}`
|
|
119
119
|
};
|
|
120
120
|
} catch (err) {
|
|
121
121
|
return {
|
|
122
|
-
ok:
|
|
122
|
+
ok: true,
|
|
123
123
|
result: {
|
|
124
|
-
|
|
124
|
+
...result,
|
|
125
125
|
approved: false,
|
|
126
|
-
blocking_issues: [
|
|
126
|
+
blocking_issues: [{
|
|
127
|
+
id: "PARSE_ERROR",
|
|
128
|
+
severity: "high",
|
|
129
|
+
description: `Reviewer output could not be parsed: ${err.message}`
|
|
130
|
+
}],
|
|
131
|
+
non_blocking_suggestions: [],
|
|
132
|
+
confidence: 0,
|
|
133
|
+
raw_summary: `Parse error: ${err.message}`
|
|
127
134
|
},
|
|
128
135
|
summary: `Reviewer output parse error: ${err.message}`
|
|
129
136
|
};
|
package/src/utils/budget.js
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import { calculateUsageCostUsd, DEFAULT_MODEL_PRICING, mergePricing } from "./pricing.js";
|
|
2
2
|
|
|
3
|
+
export function extractUsageMetrics(result, defaultModel = null) {
|
|
4
|
+
const usage = result?.usage || result?.metrics || {};
|
|
5
|
+
const tokens_in =
|
|
6
|
+
result?.tokens_in ??
|
|
7
|
+
usage?.tokens_in ??
|
|
8
|
+
usage?.input_tokens ??
|
|
9
|
+
usage?.prompt_tokens ??
|
|
10
|
+
0;
|
|
11
|
+
const tokens_out =
|
|
12
|
+
result?.tokens_out ??
|
|
13
|
+
usage?.tokens_out ??
|
|
14
|
+
usage?.output_tokens ??
|
|
15
|
+
usage?.completion_tokens ??
|
|
16
|
+
0;
|
|
17
|
+
const cost_usd =
|
|
18
|
+
result?.cost_usd ??
|
|
19
|
+
usage?.cost_usd ??
|
|
20
|
+
usage?.usd_cost ??
|
|
21
|
+
usage?.cost;
|
|
22
|
+
const model =
|
|
23
|
+
result?.model ??
|
|
24
|
+
usage?.model ??
|
|
25
|
+
usage?.model_name ??
|
|
26
|
+
usage?.model_id ??
|
|
27
|
+
defaultModel ??
|
|
28
|
+
null;
|
|
29
|
+
|
|
30
|
+
return { tokens_in, tokens_out, cost_usd, model };
|
|
31
|
+
}
|
|
32
|
+
|
|
3
33
|
function toSafeNumber(value) {
|
|
4
34
|
const n = Number(value);
|
|
5
35
|
if (!Number.isFinite(n) || n < 0) return 0;
|
package/src/utils/pricing.js
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
"claude/opus": { input_per_million: 15, output_per_million: 75 },
|
|
5
|
-
"claude/haiku": { input_per_million: 0.25, output_per_million: 1.25 },
|
|
6
|
-
"codex": { input_per_million: 1.5, output_per_million: 4 },
|
|
7
|
-
"codex/o4-mini": { input_per_million: 1.5, output_per_million: 4 },
|
|
8
|
-
"codex/o3": { input_per_million: 10, output_per_million: 40 },
|
|
9
|
-
"gemini": { input_per_million: 1.25, output_per_million: 5 },
|
|
10
|
-
"gemini/pro": { input_per_million: 1.25, output_per_million: 5 },
|
|
11
|
-
"gemini/flash": { input_per_million: 0.075, output_per_million: 0.3 },
|
|
12
|
-
"aider": { input_per_million: 3, output_per_million: 15 }
|
|
13
|
-
};
|
|
1
|
+
import { buildDefaultPricingTable } from "../agents/model-registry.js";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_MODEL_PRICING = buildDefaultPricingTable();
|
|
14
4
|
|
|
15
5
|
export function calculateUsageCostUsd({ model, tokens_in, tokens_out, pricing }) {
|
|
16
6
|
const table = pricing || DEFAULT_MODEL_PRICING;
|