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.
Files changed (115) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -1
  3. package/scripts/install.js +18 -15
  4. package/src/agents/aider-agent.js +0 -7
  5. package/src/agents/claude-agent.js +34 -6
  6. package/src/agents/codex-agent.js +2 -6
  7. package/src/agents/gemini-agent.js +0 -4
  8. package/src/agents/opencode-agent.js +0 -4
  9. package/src/{becaria → ci}/dispatch.js +13 -13
  10. package/src/{becaria → ci}/pr-diff.js +1 -1
  11. package/src/cli.js +3 -2
  12. package/src/commands/agents.js +0 -1
  13. package/src/commands/doctor.js +22 -40
  14. package/src/commands/init.js +8 -8
  15. package/src/commands/report.js +0 -13
  16. package/src/commands/review.js +17 -17
  17. package/src/commands/run.js +1 -2
  18. package/src/commands/status.js +1 -7
  19. package/src/config.js +10 -52
  20. package/src/domains/domain-loader.js +85 -4
  21. package/src/git/automation.js +9 -9
  22. package/src/guards/output-guard.js +0 -17
  23. package/src/mcp/handlers/direct-handlers.js +1 -1
  24. package/src/mcp/handlers/run-handler.js +1 -2
  25. package/src/mcp/sovereignty-guard.js +2 -2
  26. package/src/mcp/tools.js +3 -2
  27. package/src/orchestrator/brain-coordinator.js +165 -0
  28. package/src/orchestrator/ci-integration.js +139 -0
  29. package/src/orchestrator/config-init.js +435 -0
  30. package/src/orchestrator/direct-actions.js +186 -0
  31. package/src/orchestrator/feedback-enrichment.js +140 -0
  32. package/src/orchestrator/feedback-queue.js +152 -0
  33. package/src/orchestrator/flow-control.js +159 -0
  34. package/src/orchestrator/post-loop-stages.js +20 -15
  35. package/src/orchestrator/preflight-checks.js +7 -3
  36. package/src/orchestrator/role-output-compressor.js +190 -0
  37. package/src/orchestrator/session-journal.js +344 -0
  38. package/src/orchestrator/solomon-escalation.js +38 -5
  39. package/src/orchestrator/solomon-rules.js +32 -33
  40. package/src/orchestrator/stages/reviewer-stage.js +16 -5
  41. package/src/orchestrator/stages/sonar-stage.js +24 -1
  42. package/src/orchestrator/verification-gate.js +146 -0
  43. package/src/orchestrator.js +435 -661
  44. package/src/planning-game/client.js +2 -2
  45. package/src/planning-game/pipeline-adapter.js +1 -1
  46. package/src/prompts/coder.js +17 -3
  47. package/src/prompts/reviewer.js +2 -2
  48. package/src/prompts/rtk-snippet.js +3 -5
  49. package/src/review/diff-generator.js +66 -17
  50. package/src/review/snapshot-diff.js +122 -0
  51. package/src/review/tdd-policy.js +4 -2
  52. package/src/roles/agent-role.js +135 -0
  53. package/src/roles/architect-role.js +61 -108
  54. package/src/roles/audit-role.js +25 -71
  55. package/src/roles/coder-role.js +32 -57
  56. package/src/roles/discover-role.js +30 -71
  57. package/src/roles/hu-reviewer-role.js +51 -99
  58. package/src/roles/impeccable-role.js +34 -91
  59. package/src/roles/index.js +2 -0
  60. package/src/roles/karajan-brain-role.js +134 -0
  61. package/src/roles/planner-role.js +40 -75
  62. package/src/roles/refactorer-role.js +22 -64
  63. package/src/roles/researcher-role.js +52 -118
  64. package/src/roles/reviewer-role.js +69 -126
  65. package/src/roles/security-role.js +38 -102
  66. package/src/roles/solomon-role.js +71 -15
  67. package/src/roles/tester-role.js +121 -95
  68. package/src/roles/triage-role.js +50 -111
  69. package/src/session-store.js +1 -1
  70. package/src/skills/skill-detector.js +11 -0
  71. package/src/sonar/config-resolver.js +20 -1
  72. package/src/sonar/credentials.js +17 -0
  73. package/src/sonar/project-key.js +1 -1
  74. package/src/sonar/scanner.js +1 -1
  75. package/src/utils/banner.js +66 -0
  76. package/src/utils/display.js +35 -17
  77. package/src/utils/injection-guard.js +23 -8
  78. package/src/utils/logger.js +6 -1
  79. package/src/utils/process.js +3 -4
  80. package/src/utils/role-assigner.js +131 -0
  81. package/src/utils/update-check.js +5 -1
  82. package/src/utils/welcome.js +3 -8
  83. package/src/webperf/devtools-detect.js +70 -23
  84. package/templates/roles/coder.md +7 -0
  85. package/templates/roles/karajan-brain.md +95 -0
  86. package/templates/roles/solomon.md +67 -106
  87. package/templates/workflows/automerge.yml +4 -4
  88. package/templates/workflows/houston-override.yml +4 -4
  89. package/templates/workflows/{becaria-gateway.yml → kj-ci-gateway.yml} +12 -12
  90. package/src/proxy/adapters/anthropic.js +0 -131
  91. package/src/proxy/adapters/gemini.js +0 -106
  92. package/src/proxy/adapters/index.js +0 -51
  93. package/src/proxy/adapters/openai.js +0 -71
  94. package/src/proxy/compression/ai-compressor.js +0 -209
  95. package/src/proxy/compression/dedup-cache.js +0 -100
  96. package/src/proxy/compression/deterministic/bash-build.js +0 -60
  97. package/src/proxy/compression/deterministic/bash-git.js +0 -101
  98. package/src/proxy/compression/deterministic/bash-infra.js +0 -64
  99. package/src/proxy/compression/deterministic/bash-misc.js +0 -53
  100. package/src/proxy/compression/deterministic/bash-pkg.js +0 -49
  101. package/src/proxy/compression/deterministic/bash-test.js +0 -61
  102. package/src/proxy/compression/deterministic/glob.js +0 -26
  103. package/src/proxy/compression/deterministic/grep.js +0 -53
  104. package/src/proxy/compression/deterministic/index.js +0 -58
  105. package/src/proxy/compression/deterministic/read.js +0 -22
  106. package/src/proxy/compression/deterministic/utils.js +0 -33
  107. package/src/proxy/compression/pipeline.js +0 -270
  108. package/src/proxy/middleware/prompt-injector.js +0 -165
  109. package/src/proxy/middleware/response-interceptor.js +0 -223
  110. package/src/proxy/progress-monitor.js +0 -98
  111. package/src/proxy/proxy-lifecycle.js +0 -198
  112. package/src/proxy/proxy-server.js +0 -239
  113. package/src/proxy/security.js +0 -45
  114. /package/src/{becaria → ci}/index.js +0 -0
  115. /package/src/{becaria → ci}/repo.js +0 -0
@@ -1,112 +1,64 @@
1
- import { BaseRole } from "./base-role.js";
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
- * Build a human-readable summary from parsed evaluations.
21
- * @param {object} parsed - Parsed HU reviewer output.
22
- * @returns {string}
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
- export class HuReviewerRole extends BaseRole {
34
- /**
35
- * @param {{config: object, logger: object, emitter?: object|null, createAgentFn?: Function|null}} params
36
- */
37
- constructor({ config, logger, emitter = null, createAgentFn = null }) {
38
- super({ name: "hu-reviewer", config, logger, emitter });
39
- this._createAgent = createAgentFn || defaultCreateAgent;
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
- * Execute the HU review.
44
- * @param {{stories: Array<{id: string, text: string}>, context?: string|null, onOutput?: Function|null}} input
45
- * @returns {Promise<{ok: boolean, result: object, summary: string, usage?: object}>}
46
- */
47
- async execute(input) {
48
- const stories = input?.stories || [];
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
- const provider = resolveProvider(this.config);
53
- const agent = this._createAgent(provider, this.config, this.logger);
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
- const prompt = buildHuReviewerPrompt({ stories, instructions: this.instructions, context, productContext: this.config?.productContext || null, domainContext: this.config?.domainContext || null });
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
- if (!result.ok) {
61
- return {
62
- ok: false,
63
- result: {
64
- error: result.error || result.output || "HU Review failed",
65
- provider
66
- },
67
- summary: `HU Review failed: ${result.error || "unknown error"}`,
68
- usage: result.usage
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
- try {
73
- const parsed = parseHuReviewerOutput(result.output);
74
- if (!parsed) {
75
- return {
76
- ok: true,
77
- result: {
78
- evaluations: [],
79
- batch_summary: { total: 0, certified: 0, needs_rewrite: 0, needs_context: 0, consolidated_questions: "" },
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
- return {
89
- ok: true,
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 { BaseRole } from "./base-role.js";
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 BaseRole {
62
- constructor({ config, logger, emitter = null, createAgentFn = null, mode = "audit" }) {
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, config, logger, emitter });
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 { task, diff, projectDir } = typeof input === "string"
71
- ? { task: input, diff: null, projectDir: null }
72
- : { task: input?.task || this.context?.task || "", diff: input?.diff || null, projectDir: input?.projectDir || null };
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?.info?.("Chrome DevTools MCP not configured — skipping WebPerf skills");
49
+ this.logger?.debug?.("Chrome DevTools MCP not configured — WebPerf skills skipped");
87
50
  }
88
51
 
89
- const provider = resolveProvider(this.config);
90
- const agent = this._createAgent(provider, this.config, this.logger);
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 webperfNote = isDevToolsMcpAvailable(this.config)
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 = parseImpeccableOutput(result.output);
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
  }
@@ -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 { BaseRole } from "./base-role.js";
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 buildPrompt({ task, instructions, research, triageDecomposition, architectContext, productContext = null, domainContext = null }) {
67
- const sections = [];
68
-
69
- if (instructions) {
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
- return sections.join("\n");
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 BaseRole {
97
- constructor({ config, logger, emitter = null, createAgentFn = null }) {
98
- super({ name: "planner", config, logger, emitter });
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
- const taskStr = task || this.context?.task || "";
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 runArgs = { prompt, role: "planner" };
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
- const silenceTimeoutMs = resolvePlannerSilenceTimeoutMs(this.config);
118
- if (silenceTimeoutMs) runArgs.silenceTimeoutMs = silenceTimeoutMs;
119
- const timeoutMs = resolvePlannerRuntimeTimeoutMs(this.config);
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) {