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.
@@ -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.",
@@ -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);
@@ -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
- approved,
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: false,
122
+ ok: true,
123
123
  result: {
124
- error: `Failed to parse reviewer output: ${err.message}`,
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
  };
@@ -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;
@@ -1,16 +1,6 @@
1
- export const DEFAULT_MODEL_PRICING = {
2
- "claude": { input_per_million: 3, output_per_million: 15 },
3
- "claude/sonnet": { input_per_million: 3, output_per_million: 15 },
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;