karajan-code 1.49.0 → 1.50.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.
- package/package.json +1 -1
- package/src/mcp/handlers/direct-handlers.js +393 -0
- package/src/mcp/handlers/hu-handlers.js +93 -0
- package/src/mcp/handlers/management-handlers.js +182 -0
- package/src/mcp/handlers/run-handler.js +283 -0
- package/src/mcp/server-handlers.js +40 -870
- package/src/orchestrator/iteration-stages.js +9 -890
- package/src/orchestrator/pre-loop-stages.js +8 -906
- package/src/orchestrator/stages/architect-stage.js +176 -0
- package/src/orchestrator/stages/coder-stage.js +307 -0
- package/src/orchestrator/stages/hu-reviewer-stage.js +467 -0
- package/src/orchestrator/stages/planner-stage.js +90 -0
- package/src/orchestrator/stages/research-stage.js +74 -0
- package/src/orchestrator/stages/reviewer-stage.js +350 -0
- package/src/orchestrator/stages/sonar-stage.js +267 -0
- package/src/orchestrator/stages/triage-stage.js +137 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architect stage logic.
|
|
3
|
+
* Extracted from pre-loop-stages.js for maintainability.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ArchitectRole } from "../../roles/architect-role.js";
|
|
7
|
+
import { createAgent } from "../../agents/index.js";
|
|
8
|
+
import { createArchitectADRs } from "../../planning-game/architect-adrs.js";
|
|
9
|
+
import { addCheckpoint } from "../../session-store.js";
|
|
10
|
+
import { emitProgress, makeEvent } from "../../utils/events.js";
|
|
11
|
+
import { createStallDetector } from "../../utils/stall-detector.js";
|
|
12
|
+
|
|
13
|
+
async function handleArchitectClarification({ architectOutput, askQuestion, config, logger, emitter, eventBase, session, architectOnOutput, architectProvider, coderRole, researchContext, discoverResult, triageLevel, trackBudget }) {
|
|
14
|
+
if (!architectOutput.ok
|
|
15
|
+
|| architectOutput.result?.verdict !== "needs_clarification"
|
|
16
|
+
|| !architectOutput.result?.questions?.length) {
|
|
17
|
+
return architectOutput;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const questions = architectOutput.result.questions;
|
|
21
|
+
if (!askQuestion) {
|
|
22
|
+
logger.warn("Architect returned needs_clarification but no interactive input available — continuing with best-effort decisions");
|
|
23
|
+
return architectOutput;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const formatted = "The architect needs clarification before proceeding:\n\n"
|
|
27
|
+
+ questions.map((q, i) => `${i + 1}. ${q}`).join("\n")
|
|
28
|
+
+ "\n\nPlease provide your answers:";
|
|
29
|
+
|
|
30
|
+
emitProgress(
|
|
31
|
+
emitter,
|
|
32
|
+
makeEvent("architect:clarification", { ...eventBase, stage: "architect" }, {
|
|
33
|
+
message: "Architect needs clarification — pausing for human input",
|
|
34
|
+
detail: { questions }
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const answer = await askQuestion(formatted, { iteration: 0, stage: "architect" });
|
|
39
|
+
if (!answer) return architectOutput;
|
|
40
|
+
|
|
41
|
+
const architect2 = new ArchitectRole({ config, logger, emitter, createAgentFn: createAgent });
|
|
42
|
+
await architect2.init({ task: session.task, sessionId: session.id, iteration: 0 });
|
|
43
|
+
const rerunStart = Date.now();
|
|
44
|
+
const rerunStall = createStallDetector({
|
|
45
|
+
onOutput: architectOnOutput, emitter, eventBase, stage: "architect", provider: architectProvider
|
|
46
|
+
});
|
|
47
|
+
let result;
|
|
48
|
+
try {
|
|
49
|
+
result = await architect2.execute({
|
|
50
|
+
task: session.task,
|
|
51
|
+
onOutput: rerunStall.onOutput,
|
|
52
|
+
researchContext,
|
|
53
|
+
discoverResult,
|
|
54
|
+
triageLevel,
|
|
55
|
+
humanAnswers: answer
|
|
56
|
+
});
|
|
57
|
+
} finally {
|
|
58
|
+
rerunStall.stop();
|
|
59
|
+
}
|
|
60
|
+
trackBudget({
|
|
61
|
+
role: "architect",
|
|
62
|
+
provider: architectProvider,
|
|
63
|
+
model: config?.roles?.architect?.model || coderRole.model,
|
|
64
|
+
result,
|
|
65
|
+
duration_ms: Date.now() - rerunStart
|
|
66
|
+
});
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function runArchitectStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget, researchContext = null, discoverResult = null, triageLevel = null, askQuestion = null }) {
|
|
71
|
+
logger.setContext({ iteration: 0, stage: "architect" });
|
|
72
|
+
const architectProvider = config?.roles?.architect?.provider || coderRole.provider;
|
|
73
|
+
emitProgress(
|
|
74
|
+
emitter,
|
|
75
|
+
makeEvent("architect:start", { ...eventBase, stage: "architect" }, {
|
|
76
|
+
message: "Architect designing solution architecture",
|
|
77
|
+
detail: { architect: architectProvider, provider: architectProvider, executorType: "agent" }
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
const architectOnOutput = ({ stream, line }) => {
|
|
81
|
+
emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "architect" }, {
|
|
82
|
+
message: line,
|
|
83
|
+
detail: { stream, agent: architectProvider }
|
|
84
|
+
}));
|
|
85
|
+
};
|
|
86
|
+
const architectStall = createStallDetector({
|
|
87
|
+
onOutput: architectOnOutput, emitter, eventBase, stage: "architect", provider: architectProvider
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const architect = new ArchitectRole({ config, logger, emitter, createAgentFn: createAgent });
|
|
91
|
+
await architect.init({ task: session.task, sessionId: session.id, iteration: 0 });
|
|
92
|
+
const architectStart = Date.now();
|
|
93
|
+
let architectOutput;
|
|
94
|
+
try {
|
|
95
|
+
architectOutput = await architect.execute({
|
|
96
|
+
task: session.task,
|
|
97
|
+
onOutput: architectStall.onOutput,
|
|
98
|
+
researchContext,
|
|
99
|
+
discoverResult,
|
|
100
|
+
triageLevel
|
|
101
|
+
});
|
|
102
|
+
} catch (err) {
|
|
103
|
+
logger.warn(`Architect threw: ${err.message}`);
|
|
104
|
+
architectOutput = { ok: false, summary: `Architect error: ${err.message}`, result: { error: err.message } };
|
|
105
|
+
} finally {
|
|
106
|
+
architectStall.stop();
|
|
107
|
+
}
|
|
108
|
+
trackBudget({
|
|
109
|
+
role: "architect",
|
|
110
|
+
provider: architectProvider,
|
|
111
|
+
model: config?.roles?.architect?.model || coderRole.model,
|
|
112
|
+
result: architectOutput,
|
|
113
|
+
duration_ms: Date.now() - architectStart
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await addCheckpoint(session, {
|
|
117
|
+
stage: "architect",
|
|
118
|
+
iteration: 0,
|
|
119
|
+
ok: architectOutput.ok,
|
|
120
|
+
provider: architectProvider,
|
|
121
|
+
model: config?.roles?.architect?.model || coderRole.model || null
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// --- Interactive clarification loop ---
|
|
125
|
+
architectOutput = await handleArchitectClarification({
|
|
126
|
+
architectOutput, askQuestion, config, logger, emitter, eventBase, session,
|
|
127
|
+
architectOnOutput, architectProvider, coderRole, researchContext, discoverResult, triageLevel, trackBudget
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const stageResult = {
|
|
131
|
+
ok: architectOutput.ok,
|
|
132
|
+
verdict: architectOutput.result?.verdict || null,
|
|
133
|
+
architecture: architectOutput.result?.architecture || null,
|
|
134
|
+
questions: architectOutput.result?.questions || []
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
emitProgress(
|
|
138
|
+
emitter,
|
|
139
|
+
makeEvent("architect:end", { ...eventBase, stage: "architect" }, {
|
|
140
|
+
status: architectOutput.ok ? "ok" : "fail",
|
|
141
|
+
message: architectOutput.ok ? "Architecture completed" : `Architecture failed: ${architectOutput.summary}`,
|
|
142
|
+
detail: { ...stageResult, provider: architectProvider, executorType: "agent" }
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const architectContext = architectOutput.ok ? architectOutput.result : null;
|
|
147
|
+
|
|
148
|
+
// TODO: Move ADR creation to planning-game/pipeline-adapter.js (PG coupling still here because
|
|
149
|
+
// stageResult.adrs is consumed synchronously within runArchitectStage's return value).
|
|
150
|
+
// Generate ADRs from architect tradeoffs when PG is linked
|
|
151
|
+
const tradeoffs = architectOutput.result?.architecture?.tradeoffs;
|
|
152
|
+
if (architectOutput.ok
|
|
153
|
+
&& architectOutput.result?.verdict === "ready"
|
|
154
|
+
&& tradeoffs?.length > 0
|
|
155
|
+
&& session.pg_task_id
|
|
156
|
+
&& session.pg_project_id) {
|
|
157
|
+
try {
|
|
158
|
+
const pgClient = await import("../../planning-game/client.js");
|
|
159
|
+
const adrResult = await createArchitectADRs({
|
|
160
|
+
tradeoffs,
|
|
161
|
+
pgTaskId: session.pg_task_id,
|
|
162
|
+
pgProject: session.pg_project_id,
|
|
163
|
+
taskTitle: session.task,
|
|
164
|
+
mcpClient: pgClient
|
|
165
|
+
});
|
|
166
|
+
stageResult.adrs = adrResult;
|
|
167
|
+
if (adrResult.created > 0) {
|
|
168
|
+
logger.info(`Architect: created ${adrResult.created} ADR(s) in Planning Game`);
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
logger.warn(`Architect: failed to create ADRs: ${err.message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { architectContext, stageResult };
|
|
176
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coder stage logic.
|
|
3
|
+
* Extracted from iteration-stages.js for maintainability.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createAgent } from "../../agents/index.js";
|
|
7
|
+
import { CoderRole } from "../../roles/coder-role.js";
|
|
8
|
+
import { RefactorerRole } from "../../roles/refactorer-role.js";
|
|
9
|
+
import { addCheckpoint, markSessionStatus, saveSession } from "../../session-store.js";
|
|
10
|
+
import { generateDiff, getUntrackedFiles } from "../../review/diff-generator.js";
|
|
11
|
+
import { evaluateTddPolicy } from "../../review/tdd-policy.js";
|
|
12
|
+
import { buildDeferredContext } from "../../review/scope-filter.js";
|
|
13
|
+
import { emitProgress, makeEvent } from "../../utils/events.js";
|
|
14
|
+
import { runCoderWithFallback } from "../agent-fallback.js";
|
|
15
|
+
import { invokeSolomon } from "../solomon-escalation.js";
|
|
16
|
+
import { detectRateLimit } from "../../utils/rate-limit-detector.js";
|
|
17
|
+
import { createStallDetector } from "../../utils/stall-detector.js";
|
|
18
|
+
|
|
19
|
+
export async function runCoderStage({ coderRoleInstance, coderRole, config, logger, emitter, eventBase, session, plannedTask, trackBudget, iteration }) {
|
|
20
|
+
logger.setContext({ iteration, stage: "coder" });
|
|
21
|
+
emitProgress(
|
|
22
|
+
emitter,
|
|
23
|
+
makeEvent("coder:start", { ...eventBase, stage: "coder" }, {
|
|
24
|
+
message: `Coder (${coderRole.provider}) running`,
|
|
25
|
+
detail: { coder: coderRole.provider, provider: coderRole.provider, executorType: "agent" }
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const coderOnOutput = ({ stream, line }) => {
|
|
30
|
+
emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "coder" }, {
|
|
31
|
+
message: line,
|
|
32
|
+
detail: { stream, agent: coderRole.provider }
|
|
33
|
+
}));
|
|
34
|
+
};
|
|
35
|
+
const coderStall = createStallDetector({
|
|
36
|
+
onOutput: coderOnOutput, emitter, eventBase, stage: "coder", provider: coderRole.provider
|
|
37
|
+
});
|
|
38
|
+
const coderStart = Date.now();
|
|
39
|
+
let coderExecResult;
|
|
40
|
+
try {
|
|
41
|
+
coderExecResult = await coderRoleInstance.execute({
|
|
42
|
+
task: plannedTask,
|
|
43
|
+
reviewerFeedback: session.last_reviewer_feedback,
|
|
44
|
+
sonarSummary: session.last_sonar_summary,
|
|
45
|
+
deferredContext: buildDeferredContext(session.deferred_issues),
|
|
46
|
+
onOutput: coderStall.onOutput
|
|
47
|
+
});
|
|
48
|
+
} finally {
|
|
49
|
+
coderStall.stop();
|
|
50
|
+
}
|
|
51
|
+
trackBudget({ role: "coder", provider: coderRole.provider, model: coderRole.model, result: coderExecResult.result, duration_ms: Date.now() - coderStart });
|
|
52
|
+
|
|
53
|
+
if (!coderExecResult.ok) {
|
|
54
|
+
const details = coderExecResult.result?.error || coderExecResult.summary || "unknown error";
|
|
55
|
+
const rateLimitCheck = detectRateLimit({
|
|
56
|
+
stderr: coderExecResult.result?.error || "",
|
|
57
|
+
stdout: coderExecResult.result?.output || ""
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (rateLimitCheck.isRateLimit) {
|
|
61
|
+
// Try fallback agent if configured
|
|
62
|
+
const fallbackCoder = config.coder_options?.fallback_coder;
|
|
63
|
+
if (fallbackCoder && fallbackCoder !== coderRole.provider) {
|
|
64
|
+
logger.warn(`Coder ${coderRole.provider} hit rate limit, falling back to ${fallbackCoder}`);
|
|
65
|
+
emitProgress(
|
|
66
|
+
emitter,
|
|
67
|
+
makeEvent("coder:fallback", { ...eventBase, stage: "coder" }, {
|
|
68
|
+
message: `Coder ${coderRole.provider} rate-limited, switching to ${fallbackCoder}`,
|
|
69
|
+
detail: { primary: coderRole.provider, fallback: fallbackCoder }
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const fallbackResult = await runCoderWithFallback({
|
|
74
|
+
coderName: fallbackCoder,
|
|
75
|
+
fallbackCoder: null,
|
|
76
|
+
config,
|
|
77
|
+
logger,
|
|
78
|
+
emitter,
|
|
79
|
+
RoleClass: CoderRole,
|
|
80
|
+
roleInput: { task: plannedTask, reviewerFeedback: session.last_reviewer_feedback, sonarSummary: session.last_sonar_summary, onOutput: coderOnOutput },
|
|
81
|
+
session,
|
|
82
|
+
iteration,
|
|
83
|
+
onAttemptResult: ({ coder, result }) => {
|
|
84
|
+
trackBudget({ role: "coder", provider: coder, model: coderRole.model, result, duration_ms: Date.now() - coderStart });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (fallbackResult.execResult?.ok) {
|
|
89
|
+
await addCheckpoint(session, { stage: "coder", iteration, note: `Coder completed via fallback (${fallbackCoder})`, provider: fallbackCoder, model: null });
|
|
90
|
+
emitProgress(
|
|
91
|
+
emitter,
|
|
92
|
+
makeEvent("coder:end", { ...eventBase, stage: "coder" }, {
|
|
93
|
+
message: `Coder completed (fallback: ${fallbackCoder})`,
|
|
94
|
+
detail: { provider: fallbackCoder, executorType: "agent" }
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// No fallback or fallback also failed — enter standby
|
|
102
|
+
return {
|
|
103
|
+
action: "standby",
|
|
104
|
+
standbyInfo: {
|
|
105
|
+
agent: coderRole.provider,
|
|
106
|
+
cooldownMs: rateLimitCheck.cooldownMs || (rateLimitCheck.isProviderOutage ? 30000 : null),
|
|
107
|
+
cooldownUntil: rateLimitCheck.cooldownUntil,
|
|
108
|
+
message: rateLimitCheck.message,
|
|
109
|
+
isProviderOutage: rateLimitCheck.isProviderOutage || false
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await markSessionStatus(session, "failed");
|
|
115
|
+
emitProgress(
|
|
116
|
+
emitter,
|
|
117
|
+
makeEvent("coder:end", { ...eventBase, stage: "coder" }, {
|
|
118
|
+
status: "fail",
|
|
119
|
+
message: `Coder failed: ${details}`,
|
|
120
|
+
detail: { provider: coderRole.provider, executorType: "agent" }
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
throw new Error(`Coder failed: ${details}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await addCheckpoint(session, { stage: "coder", iteration, note: "Coder applied changes", provider: coderRole.provider, model: coderRole.model || null });
|
|
127
|
+
emitProgress(
|
|
128
|
+
emitter,
|
|
129
|
+
makeEvent("coder:end", { ...eventBase, stage: "coder" }, {
|
|
130
|
+
message: "Coder completed",
|
|
131
|
+
detail: { provider: coderRole.provider, executorType: "agent" }
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function runRefactorerStage({ refactorerRole, config, logger, emitter, eventBase, session, plannedTask, trackBudget, iteration }) {
|
|
137
|
+
logger.setContext({ iteration, stage: "refactorer" });
|
|
138
|
+
emitProgress(
|
|
139
|
+
emitter,
|
|
140
|
+
makeEvent("refactorer:start", { ...eventBase, stage: "refactorer" }, {
|
|
141
|
+
message: `Refactorer (${refactorerRole.provider}) running`,
|
|
142
|
+
detail: { refactorer: refactorerRole.provider, provider: refactorerRole.provider, executorType: "agent" }
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
const refactorerOnOutput = ({ stream, line }) => {
|
|
146
|
+
emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "refactorer" }, {
|
|
147
|
+
message: line,
|
|
148
|
+
detail: { stream, agent: refactorerRole.provider }
|
|
149
|
+
}));
|
|
150
|
+
};
|
|
151
|
+
const refactorerStall = createStallDetector({
|
|
152
|
+
onOutput: refactorerOnOutput, emitter, eventBase, stage: "refactorer", provider: refactorerRole.provider
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const refRole = new RefactorerRole({ config, logger, emitter, createAgentFn: createAgent });
|
|
156
|
+
await refRole.init();
|
|
157
|
+
const refactorerStart = Date.now();
|
|
158
|
+
let refResult;
|
|
159
|
+
try {
|
|
160
|
+
refResult = await refRole.execute({ task: plannedTask, onOutput: refactorerStall.onOutput });
|
|
161
|
+
} finally {
|
|
162
|
+
refactorerStall.stop();
|
|
163
|
+
}
|
|
164
|
+
trackBudget({ role: "refactorer", provider: refactorerRole.provider, model: refactorerRole.model, result: refResult.result, duration_ms: Date.now() - refactorerStart });
|
|
165
|
+
if (!refResult.ok) {
|
|
166
|
+
const details = refResult.result?.error || refResult.summary || "unknown error";
|
|
167
|
+
const rateLimitCheck = detectRateLimit({
|
|
168
|
+
stderr: refResult.result?.error || "",
|
|
169
|
+
stdout: refResult.result?.output || ""
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (rateLimitCheck.isRateLimit) {
|
|
173
|
+
// Enter standby instead of pausing
|
|
174
|
+
return {
|
|
175
|
+
action: "standby",
|
|
176
|
+
standbyInfo: {
|
|
177
|
+
agent: refactorerRole.provider,
|
|
178
|
+
cooldownMs: rateLimitCheck.cooldownMs || (rateLimitCheck.isProviderOutage ? 30000 : null),
|
|
179
|
+
cooldownUntil: rateLimitCheck.cooldownUntil,
|
|
180
|
+
message: rateLimitCheck.message,
|
|
181
|
+
isProviderOutage: rateLimitCheck.isProviderOutage || false
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await markSessionStatus(session, "failed");
|
|
187
|
+
emitProgress(
|
|
188
|
+
emitter,
|
|
189
|
+
makeEvent("refactorer:end", { ...eventBase, stage: "refactorer" }, {
|
|
190
|
+
status: "fail",
|
|
191
|
+
message: `Refactorer failed: ${details}`,
|
|
192
|
+
detail: { provider: refactorerRole.provider, executorType: "agent" }
|
|
193
|
+
})
|
|
194
|
+
);
|
|
195
|
+
throw new Error(`Refactorer failed: ${details}`);
|
|
196
|
+
}
|
|
197
|
+
await addCheckpoint(session, { stage: "refactorer", iteration, note: "Refactorer applied cleanups", provider: refactorerRole.provider, model: refactorerRole.model || null });
|
|
198
|
+
emitProgress(
|
|
199
|
+
emitter,
|
|
200
|
+
makeEvent("refactorer:end", { ...eventBase, stage: "refactorer" }, {
|
|
201
|
+
message: "Refactorer completed",
|
|
202
|
+
detail: { provider: refactorerRole.provider, executorType: "agent" }
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function handleSolomonAction(solomonResult, session, contextPrefix) {
|
|
208
|
+
if (solomonResult.action === "pause") {
|
|
209
|
+
return { action: "pause", result: { paused: true, sessionId: session.id, question: solomonResult.question, context: `${contextPrefix}_fail_fast` } };
|
|
210
|
+
}
|
|
211
|
+
if (solomonResult.action === "subtask") {
|
|
212
|
+
return { action: "pause", result: { paused: true, sessionId: session.id, subtask: solomonResult.subtask, context: `${contextPrefix}_subtask` } };
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function handleSolomonContinue(solomonResult, session, counterField) {
|
|
218
|
+
if (solomonResult.action !== "continue") return false;
|
|
219
|
+
if (solomonResult.humanGuidance) {
|
|
220
|
+
session.last_reviewer_feedback += `\nUser guidance: ${solomonResult.humanGuidance}`;
|
|
221
|
+
}
|
|
222
|
+
session[counterField] = 0;
|
|
223
|
+
await saveSession(session);
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function handleTddFailure({ tddEval, config, logger, emitter, eventBase, session, iteration, askQuestion }) {
|
|
228
|
+
session.last_reviewer_feedback = tddEval.message;
|
|
229
|
+
session.repeated_issue_count += 1;
|
|
230
|
+
await saveSession(session);
|
|
231
|
+
|
|
232
|
+
if (session.repeated_issue_count < config.session.fail_fast_repeats) {
|
|
233
|
+
return { action: "continue" };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
emitProgress(
|
|
237
|
+
emitter,
|
|
238
|
+
makeEvent("solomon:escalate", { ...eventBase, stage: "tdd" }, {
|
|
239
|
+
message: `TDD sub-loop limit reached (${session.repeated_issue_count}/${config.session.fail_fast_repeats})`,
|
|
240
|
+
detail: { subloop: "tdd", retryCount: session.repeated_issue_count, reason: tddEval.reason }
|
|
241
|
+
})
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const solomonResult = await invokeSolomon({
|
|
245
|
+
config, logger, emitter, eventBase, stage: "tdd", askQuestion, session, iteration,
|
|
246
|
+
conflict: {
|
|
247
|
+
stage: "tdd",
|
|
248
|
+
task: session.task,
|
|
249
|
+
iterationCount: session.repeated_issue_count,
|
|
250
|
+
maxIterations: config.session.fail_fast_repeats,
|
|
251
|
+
reason: tddEval.reason,
|
|
252
|
+
sourceFiles: tddEval.sourceFiles,
|
|
253
|
+
testFiles: tddEval.testFiles,
|
|
254
|
+
history: [{ agent: "tdd-policy", feedback: tddEval.message }]
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const actionResult = handleSolomonAction(solomonResult, session, "tdd");
|
|
259
|
+
if (actionResult) return actionResult;
|
|
260
|
+
const continued = await handleSolomonContinue(solomonResult, session, "repeated_issue_count");
|
|
261
|
+
if (continued) return { action: "continue" };
|
|
262
|
+
|
|
263
|
+
return { action: "continue" };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export async function runTddCheckStage({ config, logger, emitter, eventBase, session, trackBudget, iteration, askQuestion }) {
|
|
267
|
+
logger.setContext({ iteration, stage: "tdd" });
|
|
268
|
+
let tddDiff, untrackedFiles;
|
|
269
|
+
try {
|
|
270
|
+
tddDiff = await generateDiff({ baseRef: session.session_start_sha });
|
|
271
|
+
untrackedFiles = await getUntrackedFiles();
|
|
272
|
+
} catch (err) {
|
|
273
|
+
logger.warn(`TDD diff generation failed: ${err.message}`);
|
|
274
|
+
return { action: "continue", stageResult: { ok: false, summary: `TDD check failed: ${err.message}` } };
|
|
275
|
+
}
|
|
276
|
+
const effectiveTaskType = session.resolved_policies?.taskType || null;
|
|
277
|
+
const tddEval = evaluateTddPolicy(tddDiff, config.development, untrackedFiles, effectiveTaskType);
|
|
278
|
+
await addCheckpoint(session, {
|
|
279
|
+
stage: "tdd-policy",
|
|
280
|
+
iteration,
|
|
281
|
+
ok: tddEval.ok,
|
|
282
|
+
reason: tddEval.reason,
|
|
283
|
+
source_files: tddEval.sourceFiles?.length || 0,
|
|
284
|
+
test_files: tddEval.testFiles?.length || 0
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
emitProgress(
|
|
288
|
+
emitter,
|
|
289
|
+
makeEvent("tdd:result", { ...eventBase, stage: "tdd" }, {
|
|
290
|
+
status: tddEval.ok ? "ok" : "fail",
|
|
291
|
+
message: tddEval.ok ? "TDD policy passed" : `TDD policy failed: ${tddEval.reason}`,
|
|
292
|
+
detail: {
|
|
293
|
+
ok: tddEval.ok,
|
|
294
|
+
reason: tddEval.reason,
|
|
295
|
+
sourceFiles: tddEval.sourceFiles?.length || 0,
|
|
296
|
+
testFiles: tddEval.testFiles?.length || 0,
|
|
297
|
+
executorType: "local"
|
|
298
|
+
}
|
|
299
|
+
})
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
if (!tddEval.ok) {
|
|
303
|
+
return handleTddFailure({ tddEval, config, logger, emitter, eventBase, session, iteration, askQuestion });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return { action: "ok" };
|
|
307
|
+
}
|