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,122 +1,75 @@
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 { buildArchitectPrompt, parseArchitectOutput } from "../prompts/architect.js";
4
3
 
5
- function resolveProvider(config) {
6
- return (
7
- config?.roles?.architect?.provider ||
8
- config?.roles?.coder?.provider ||
9
- "claude"
10
- );
11
- }
12
-
13
- function buildSummary(parsed) {
14
- const parts = [];
15
- const arch = parsed.architecture;
16
-
17
- if (arch.type) parts.push(arch.type);
18
-
19
- const layers = arch.layers?.length || 0;
20
- if (layers) parts.push(`${layers} layer${layers === 1 ? "" : "s"}`);
21
-
22
- const patterns = arch.patterns?.length || 0;
23
- if (patterns) parts.push(`${patterns} pattern${patterns === 1 ? "" : "s"}`);
24
-
25
- const entities = arch.dataModel?.entities?.length || 0;
26
- if (entities) parts.push(`${entities} entit${entities === 1 ? "y" : "ies"}`);
27
-
28
- const questions = parsed.questions?.length || 0;
29
- if (questions) parts.push(`${questions} question${questions === 1 ? "" : "s"}`);
30
-
31
- return parts.length
32
- ? `Architecture complete: ${parts.join(", ")} (verdict: ${parsed.verdict})`
33
- : "Architecture complete";
34
- }
35
-
36
4
  const EMPTY_ARCHITECTURE = {
37
- type: "",
38
- layers: [],
39
- patterns: [],
40
- dataModel: { entities: [] },
41
- apiContracts: [],
42
- dependencies: [],
43
- tradeoffs: []
5
+ type: "", layers: [], patterns: [], dataModel: { entities: [] },
6
+ apiContracts: [], dependencies: [], tradeoffs: []
44
7
  };
45
8
 
46
- export class ArchitectRole extends BaseRole {
47
- constructor({ config, logger, emitter = null, createAgentFn = null }) {
48
- super({ name: "architect", config, logger, emitter });
49
- this._createAgent = createAgentFn || defaultCreateAgent;
9
+ export class ArchitectRole extends AgentRole {
10
+ constructor(opts) {
11
+ super({ ...opts, name: "architect" });
12
+ }
13
+
14
+ extractInput(input) {
15
+ if (typeof input === "string") return { task: input, researchContext: null, onOutput: null };
16
+ return {
17
+ task: input?.task || this.context?.task || "",
18
+ researchContext: input?.researchContext || null,
19
+ onOutput: input?.onOutput || null
20
+ };
50
21
  }
51
22
 
52
- async execute(input) {
53
- const task = typeof input === "string"
54
- ? input
55
- : input?.task || this.context?.task || "";
56
- const onOutput = typeof input === "string" ? null : input?.onOutput || null;
57
- const researchContext = typeof input === "object" ? input?.researchContext || null : null;
23
+ async buildPrompt({ task, researchContext }) {
24
+ const prompt = await buildArchitectPrompt({
25
+ task, instructions: this.instructions, researchContext,
26
+ productContext: this.config?.productContext || null,
27
+ domainContext: this.config?.domainContext || null
28
+ });
29
+ return { prompt };
30
+ }
58
31
 
59
- const provider = resolveProvider(this.config);
60
- const agent = this._createAgent(provider, this.config, this.logger);
32
+ parseOutput(raw) { return parseArchitectOutput(raw); }
61
33
 
62
- const prompt = await buildArchitectPrompt({ task, instructions: this.instructions, researchContext, productContext: this.config?.productContext || null, domainContext: this.config?.domainContext || null });
63
- const runArgs = { prompt, role: "architect" };
64
- if (onOutput) runArgs.onOutput = onOutput;
65
- const result = await agent.runTask(runArgs);
34
+ buildSuccessResult(parsed, provider) {
35
+ return {
36
+ verdict: parsed.verdict,
37
+ architecture: parsed.architecture,
38
+ questions: parsed.questions,
39
+ provider
40
+ };
41
+ }
66
42
 
67
- if (!result.ok) {
68
- return {
69
- ok: false,
70
- result: {
71
- error: result.error || result.output || "Architect failed",
72
- provider
73
- },
74
- summary: `Architect failed: ${result.error || "unknown error"}`,
75
- usage: result.usage
76
- };
77
- }
43
+ buildSummary(parsed) {
44
+ const parts = [];
45
+ const arch = parsed.architecture;
46
+ if (arch.type) parts.push(arch.type);
47
+ const layers = arch.layers?.length || 0;
48
+ if (layers) parts.push(`${layers} layer${layers === 1 ? "" : "s"}`);
49
+ const patterns = arch.patterns?.length || 0;
50
+ if (patterns) parts.push(`${patterns} pattern${patterns === 1 ? "" : "s"}`);
51
+ const entities = arch.dataModel?.entities?.length || 0;
52
+ if (entities) parts.push(`${entities} entit${entities === 1 ? "y" : "ies"}`);
53
+ const questions = parsed.questions?.length || 0;
54
+ if (questions) parts.push(`${questions} question${questions === 1 ? "" : "s"}`);
55
+ return parts.length
56
+ ? `Architecture complete: ${parts.join(", ")} (verdict: ${parsed.verdict})`
57
+ : "Architecture complete";
58
+ }
78
59
 
79
- try {
80
- const parsed = parseArchitectOutput(result.output);
81
- if (!parsed) {
82
- return {
83
- ok: true,
84
- result: {
85
- verdict: "needs_clarification",
86
- architecture: { ...EMPTY_ARCHITECTURE },
87
- questions: [],
88
- raw: result.output,
89
- provider
90
- },
91
- summary: "Architecture complete (unstructured output)",
92
- usage: result.usage
93
- };
94
- }
60
+ handleParseNull(agentResult, provider) {
61
+ return {
62
+ ok: true,
63
+ result: {
64
+ verdict: "needs_clarification", architecture: { ...EMPTY_ARCHITECTURE },
65
+ questions: [], raw: agentResult.output, provider
66
+ },
67
+ summary: "Architecture complete (unstructured output)",
68
+ usage: agentResult.usage
69
+ };
70
+ }
95
71
 
96
- return {
97
- ok: true,
98
- result: {
99
- verdict: parsed.verdict,
100
- architecture: parsed.architecture,
101
- questions: parsed.questions,
102
- provider
103
- },
104
- summary: buildSummary(parsed),
105
- usage: result.usage
106
- };
107
- } catch { /* agent output is not structured JSON */
108
- return {
109
- ok: true,
110
- result: {
111
- verdict: "needs_clarification",
112
- architecture: { ...EMPTY_ARCHITECTURE },
113
- questions: [],
114
- raw: result.output,
115
- provider
116
- },
117
- summary: "Architecture complete (unstructured output)",
118
- usage: result.usage
119
- };
120
- }
72
+ handleParseError(_err, agentResult, provider) {
73
+ return this.handleParseNull(agentResult, provider);
121
74
  }
122
75
  }
@@ -1,14 +1,13 @@
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 { buildAuditPrompt, parseAuditOutput, AUDIT_DIMENSIONS } from "../prompts/audit.js";
4
3
  import { measureBasalCost, loadPreviousAudit, saveAuditSnapshot, computeGrowthDelta } from "../audit/basal-cost.js";
5
4
 
6
- function resolveProvider(config) {
7
- return (
8
- config?.roles?.audit?.provider ||
9
- config?.roles?.coder?.provider ||
10
- "claude"
11
- );
5
+ function parseDimensions(dimensionsStr) {
6
+ if (!dimensionsStr || dimensionsStr === "all") return null;
7
+ const requested = dimensionsStr.split(",").map(d => d.trim().toLowerCase());
8
+ const mapped = requested.map(d => d === "quality" ? "codeQuality" : d);
9
+ const valid = mapped.filter(d => AUDIT_DIMENSIONS.includes(d));
10
+ return valid.length > 0 ? valid : null;
12
11
  }
13
12
 
14
13
  function buildSummary(parsed) {
@@ -18,45 +17,21 @@ function buildSummary(parsed) {
18
17
  if (summary.high > 0) parts.push(`${summary.high} high`);
19
18
  if (summary.medium > 0) parts.push(`${summary.medium} medium`);
20
19
  if (summary.low > 0) parts.push(`${summary.low} low`);
21
-
22
20
  const findingsStr = parts.length > 0 ? parts.join(", ") : "no issues";
23
21
  return `Overall health: ${summary.overallHealth}. ${summary.totalFindings} findings (${findingsStr})`;
24
22
  }
25
23
 
26
- function resolveInput(input, context) {
27
- if (typeof input === "string") {
28
- return { task: input, onOutput: null, dimensions: null, context: null };
29
- }
30
- return {
31
- task: input?.task || context?.task || "",
32
- onOutput: input?.onOutput || null,
33
- dimensions: input?.dimensions || null,
34
- context: input?.context || null
35
- };
36
- }
37
-
38
- function parseDimensions(dimensionsStr) {
39
- if (!dimensionsStr || dimensionsStr === "all") return null;
40
- const requested = dimensionsStr.split(",").map(d => d.trim().toLowerCase());
41
- // Map "quality" shorthand to "codeQuality"
42
- const mapped = requested.map(d => d === "quality" ? "codeQuality" : d);
43
- const valid = mapped.filter(d => AUDIT_DIMENSIONS.includes(d));
44
- return valid.length > 0 ? valid : null;
45
- }
46
-
47
- export class AuditRole extends BaseRole {
48
- constructor({ config, logger, emitter = null, createAgentFn = null }) {
49
- super({ name: "audit", config, logger, emitter });
50
- this._createAgent = createAgentFn || defaultCreateAgent;
24
+ export class AuditRole extends AgentRole {
25
+ constructor(opts) {
26
+ super({ ...opts, name: "audit" });
51
27
  }
52
28
 
53
29
  async execute(input) {
54
- const { task, onOutput, dimensions: rawDimensions, context } = resolveInput(input, this.context);
55
- const dimensions = typeof rawDimensions === "string"
56
- ? parseDimensions(rawDimensions)
57
- : rawDimensions;
58
- const provider = resolveProvider(this.config);
59
- const agent = this._createAgent(provider, this.config, this.logger);
30
+ const task = typeof input === "string" ? input : input?.task || this.context?.task || "";
31
+ const onOutput = typeof input === "string" ? null : input?.onOutput || null;
32
+ const rawDimensions = typeof input === "object" ? input?.dimensions || null : null;
33
+ const context = typeof input === "object" ? input?.context || null : null;
34
+ const dimensions = typeof rawDimensions === "string" ? parseDimensions(rawDimensions) : rawDimensions;
60
35
 
61
36
  const projectDir = this.config?.projectDir || process.cwd();
62
37
  let basalCost = null;
@@ -65,59 +40,38 @@ export class AuditRole extends BaseRole {
65
40
  basalCost = await measureBasalCost(projectDir);
66
41
  const previous = await loadPreviousAudit(projectDir);
67
42
  growthDelta = computeGrowthDelta(basalCost, previous);
68
- } catch {
69
- // basal cost is best-effort, don't fail the audit
70
- }
43
+ } catch { /* basal cost is best-effort */ }
71
44
 
45
+ const provider = this.resolveProvider();
46
+ const agent = this.createAgentInstance(provider);
72
47
  const prompt = buildAuditPrompt({ task, instructions: this.instructions, dimensions, context, basalCost, growthDelta });
73
48
  const runArgs = { prompt, role: "audit" };
74
49
  if (onOutput) runArgs.onOutput = onOutput;
75
50
  const result = await agent.runTask(runArgs);
76
51
 
77
52
  if (!result.ok) {
78
- return {
79
- ok: false,
80
- result: { error: result.error || result.output || "Audit failed", provider },
81
- summary: `Audit failed: ${result.error || "unknown error"}`,
82
- usage: result.usage
83
- };
53
+ return { ok: false, result: { error: result.error || result.output || "Audit failed", provider }, summary: `Audit failed: ${result.error || "unknown error"}`, usage: result.usage };
84
54
  }
85
55
 
86
56
  try {
87
57
  const parsed = parseAuditOutput(result.output);
88
58
  if (!parsed) {
89
- return {
90
- ok: true,
91
- result: { raw: result.output, provider },
92
- summary: "Audit complete (unstructured output)",
93
- usage: result.usage
94
- };
95
- }
96
-
97
- if (basalCost) {
98
- try { await saveAuditSnapshot(projectDir, basalCost); } catch { /* best-effort */ }
59
+ return { ok: true, result: { raw: result.output, provider }, summary: "Audit complete (unstructured output)", usage: result.usage };
99
60
  }
61
+ if (basalCost) { try { await saveAuditSnapshot(projectDir, basalCost); } catch { /* best-effort */ } }
100
62
 
101
63
  return {
102
64
  ok: true,
103
65
  result: {
104
- summary: parsed.summary,
105
- dimensions: parsed.dimensions,
66
+ summary: parsed.summary, dimensions: parsed.dimensions,
106
67
  topRecommendations: parsed.topRecommendations,
107
- basalCost: basalCost || undefined,
108
- growthDelta: growthDelta || undefined,
109
- provider
68
+ basalCost: basalCost || undefined, growthDelta: growthDelta || undefined, provider
110
69
  },
111
70
  summary: buildSummary(parsed),
112
71
  usage: result.usage
113
72
  };
114
- } catch { /* agent output is not structured JSON */
115
- return {
116
- ok: true,
117
- result: { raw: result.output, provider },
118
- summary: "Audit complete (unstructured output)",
119
- usage: result.usage
120
- };
73
+ } catch {
74
+ return { ok: true, result: { raw: result.output, provider }, summary: "Audit complete (unstructured output)", usage: result.usage };
121
75
  }
122
76
  }
123
77
  }
@@ -1,78 +1,53 @@
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 { buildCoderPrompt } from "../prompts/coder.js";
4
3
  import { isHostAgent } from "../utils/agent-detect.js";
5
4
  import { HostAgent } from "../agents/host-agent.js";
6
5
 
7
- function resolveProvider(config) {
8
- return (
9
- config?.roles?.coder?.provider ||
10
- config?.coder ||
11
- "claude"
12
- );
13
- }
14
-
15
- export class CoderRole extends BaseRole {
16
- constructor({ config, logger, emitter = null, createAgentFn = null, askHost = null }) {
17
- super({ name: "coder", config, logger, emitter });
18
- this._createAgent = createAgentFn || defaultCreateAgent;
19
- this._askHost = askHost;
6
+ export class CoderRole extends AgentRole {
7
+ constructor(opts) {
8
+ super({ ...opts, name: "coder" });
9
+ this._askHost = opts.askHost || null;
20
10
  }
21
11
 
22
- async execute(input) {
23
- const { task, reviewerFeedback, sonarSummary, deferredContext, onOutput } = typeof input === "string"
24
- ? { task: input, reviewerFeedback: null, sonarSummary: null, deferredContext: null, onOutput: null }
25
- : input || {};
26
-
27
- const provider = resolveProvider(this.config);
28
- const useHost = this._askHost && isHostAgent(provider);
29
- const agent = useHost
30
- ? new HostAgent(this.config, this.logger, { askHost: this._askHost })
31
- : this._createAgent(provider, this.config, this.logger);
12
+ resolveProvider() {
13
+ return this.config?.roles?.coder?.provider || this.config?.coder || "claude";
14
+ }
32
15
 
33
- if (useHost) {
16
+ createAgentInstance(provider) {
17
+ if (this._askHost && isHostAgent(provider)) {
34
18
  this.logger.info(`Host-as-coder: delegating to host AI (skipping ${provider} subprocess)`);
19
+ return new HostAgent(this.config, this.logger, { askHost: this._askHost });
35
20
  }
21
+ return this._createAgent(provider, this.config, this.logger);
22
+ }
23
+
24
+ extractInput(input) {
25
+ if (typeof input === "string") return { task: input, reviewerFeedback: null, sonarSummary: null, deferredContext: null, onOutput: null };
26
+ return {
27
+ task: input?.task || this.context?.task || "",
28
+ reviewerFeedback: input?.reviewerFeedback || null,
29
+ sonarSummary: input?.sonarSummary || null,
30
+ deferredContext: input?.deferredContext || null,
31
+ onOutput: input?.onOutput || null
32
+ };
33
+ }
36
34
 
35
+ async buildPrompt({ task, reviewerFeedback, sonarSummary, deferredContext }) {
37
36
  const prompt = await buildCoderPrompt({
38
- task: task || this.context?.task || "",
39
- reviewerFeedback: reviewerFeedback || null,
40
- sonarSummary: sonarSummary || null,
41
- deferredContext: deferredContext || null,
37
+ task, reviewerFeedback, sonarSummary, deferredContext,
42
38
  coderRules: this.instructions,
43
39
  methodology: this.config?.development?.methodology || "tdd",
44
40
  serenaEnabled: Boolean(this.config?.serena?.enabled),
45
41
  rtkAvailable: Boolean(this.config?.rtk?.available),
46
- proxyEnabled: Boolean(this.config?.proxy?.enabled),
47
42
  productContext: this.config?.productContext || null,
48
43
  domainContext: this.config?.domainContext || null
49
44
  });
45
+ return { prompt };
46
+ }
50
47
 
51
- const coderArgs = { prompt, role: "coder" };
52
- if (onOutput) coderArgs.onOutput = onOutput;
53
-
54
- const result = await agent.runTask(coderArgs);
55
-
56
- if (!result.ok) {
57
- return {
58
- ok: false,
59
- result: {
60
- ...result,
61
- error: result.error || result.output || "Coder failed",
62
- provider
63
- },
64
- summary: `Coder failed: ${result.error || result.output || "unknown error"}`
65
- };
66
- }
67
-
68
- return {
69
- ok: true,
70
- result: {
71
- ...result,
72
- output: result.output || "",
73
- provider
74
- },
75
- summary: "Coder completed"
76
- };
48
+ buildSuccessResult(parsed, provider, agentResult) {
49
+ return { ...agentResult, output: agentResult.output || "", provider };
77
50
  }
51
+
52
+ buildSummary() { return "Coder completed"; }
78
53
  }
@@ -1,23 +1,21 @@
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 { buildDiscoverPrompt, parseDiscoverOutput } from "../prompts/discover.js";
4
3
 
5
- function resolveProvider(config) {
6
- return (
7
- config?.roles?.discover?.provider ||
8
- config?.roles?.coder?.provider ||
9
- "claude"
10
- );
11
- }
12
-
13
4
  function pluralize(count, singular, plural) {
14
5
  return `${count} ${count === 1 ? singular : plural}`;
15
6
  }
16
7
 
17
- function collectModeParts(parsed, mode, gapCount) {
8
+ const MODE_FIELDS = {
9
+ momtest: { key: "momTestQuestions", fallback: [] },
10
+ wendel: { key: "wendelChecklist", fallback: [] },
11
+ classify: { key: "classification", fallback: null },
12
+ jtbd: { key: "jtbds", fallback: [] }
13
+ };
14
+
15
+ function buildSummary(parsed, mode) {
16
+ const gapCount = parsed.gaps?.length || 0;
18
17
  const parts = [];
19
18
  if (gapCount > 0) parts.push(`${pluralize(gapCount, "gap", "gaps")} found`);
20
-
21
19
  if (mode === "momtest") {
22
20
  const qCount = parsed.momTestQuestions?.length || 0;
23
21
  if (qCount > 0) parts.push(`${pluralize(qCount, "Mom Test question", "Mom Test questions")}`);
@@ -33,63 +31,23 @@ function collectModeParts(parsed, mode, gapCount) {
33
31
  const jCount = parsed.jtbds?.length || 0;
34
32
  if (jCount > 0) parts.push(`${pluralize(jCount, "JTBD", "JTBDs")} generated`);
35
33
  }
36
- return parts;
37
- }
38
-
39
- function buildSummary(parsed, mode) {
40
- const gapCount = parsed.gaps?.length || 0;
41
- const parts = collectModeParts(parsed, mode, gapCount);
42
34
  if (parts.length === 0) return "Discovery complete: task is ready";
43
35
  return `Discovery complete: ${parts.join(", ")} (verdict: ${parsed.verdict})`;
44
36
  }
45
37
 
46
- function resolveInput(input, context) {
47
- if (typeof input === "string") {
48
- return { task: input, onOutput: null, mode: "gaps", context: null };
49
- }
50
- return {
51
- task: input?.task || context?.task || "",
52
- onOutput: input?.onOutput || null,
53
- mode: input?.mode || "gaps",
54
- context: input?.context || null
55
- };
56
- }
57
-
58
- function buildUnstructuredResult(output, mode, provider, usage) {
59
- return {
60
- ok: true,
61
- result: { verdict: "ready", gaps: [], mode, raw: output, provider },
62
- summary: "Discovery complete (unstructured output)",
63
- usage
64
- };
65
- }
66
-
67
- const MODE_FIELDS = {
68
- momtest: { key: "momTestQuestions", fallback: [] },
69
- wendel: { key: "wendelChecklist", fallback: [] },
70
- classify: { key: "classification", fallback: null },
71
- jtbd: { key: "jtbds", fallback: [] }
72
- };
73
-
74
- function buildResultFromParsed(parsed, mode, provider) {
75
- const resultObj = { verdict: parsed.verdict, gaps: parsed.gaps, mode, provider };
76
- const fieldDef = MODE_FIELDS[mode];
77
- if (fieldDef) {
78
- resultObj[fieldDef.key] = parsed[fieldDef.key] ?? fieldDef.fallback;
79
- }
80
- return resultObj;
81
- }
82
-
83
- export class DiscoverRole extends BaseRole {
84
- constructor({ config, logger, emitter = null, createAgentFn = null }) {
85
- super({ name: "discover", config, logger, emitter });
86
- this._createAgent = createAgentFn || defaultCreateAgent;
38
+ export class DiscoverRole extends AgentRole {
39
+ constructor(opts) {
40
+ super({ ...opts, name: "discover" });
87
41
  }
88
42
 
89
43
  async execute(input) {
90
- const { task, onOutput, mode, context } = resolveInput(input, this.context);
91
- const provider = resolveProvider(this.config);
92
- const agent = this._createAgent(provider, this.config, this.logger);
44
+ const task = typeof input === "string" ? input : input?.task || this.context?.task || "";
45
+ const onOutput = typeof input === "string" ? null : input?.onOutput || null;
46
+ const mode = typeof input === "object" ? input?.mode || "gaps" : "gaps";
47
+ const context = typeof input === "object" ? input?.context || null : null;
48
+
49
+ const provider = this.resolveProvider();
50
+ const agent = this.createAgentInstance(provider);
93
51
 
94
52
  const prompt = buildDiscoverPrompt({ task, instructions: this.instructions, mode, context });
95
53
  const runArgs = { prompt, role: "discover" };
@@ -107,16 +65,17 @@ export class DiscoverRole extends BaseRole {
107
65
 
108
66
  try {
109
67
  const parsed = parseDiscoverOutput(result.output);
110
- if (!parsed) return buildUnstructuredResult(result.output, mode, provider, result.usage);
68
+ if (!parsed) {
69
+ return { ok: true, result: { verdict: "ready", gaps: [], mode, raw: result.output, provider }, summary: "Discovery complete (unstructured output)", usage: result.usage };
70
+ }
111
71
 
112
- return {
113
- ok: true,
114
- result: buildResultFromParsed(parsed, mode, provider),
115
- summary: buildSummary(parsed, mode),
116
- usage: result.usage
117
- };
118
- } catch { /* agent output is not structured JSON */
119
- return buildUnstructuredResult(result.output, mode, provider, result.usage);
72
+ const resultObj = { verdict: parsed.verdict, gaps: parsed.gaps, mode, provider };
73
+ const fieldDef = MODE_FIELDS[mode];
74
+ if (fieldDef) resultObj[fieldDef.key] = parsed[fieldDef.key] ?? fieldDef.fallback;
75
+
76
+ return { ok: true, result: resultObj, summary: buildSummary(parsed, mode), usage: result.usage };
77
+ } catch {
78
+ return { ok: true, result: { verdict: "ready", gaps: [], mode, raw: result.output, provider }, summary: "Discovery complete (unstructured output)", usage: result.usage };
120
79
  }
121
80
  }
122
81
  }