karajan-code 1.16.0 → 1.18.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/activity-log.js +13 -13
- package/src/agents/availability.js +2 -3
- package/src/agents/claude-agent.js +42 -21
- package/src/agents/model-registry.js +1 -1
- package/src/becaria/dispatch.js +1 -1
- package/src/becaria/repo.js +3 -3
- package/src/cli.js +5 -2
- package/src/commands/doctor.js +154 -108
- package/src/commands/init.js +101 -90
- package/src/commands/plan.js +1 -1
- package/src/commands/report.js +77 -71
- package/src/commands/roles.js +0 -1
- package/src/commands/run.js +2 -3
- package/src/config.js +174 -93
- package/src/git/automation.js +3 -4
- package/src/guards/intent-guard.js +123 -0
- package/src/guards/output-guard.js +158 -0
- package/src/guards/perf-guard.js +126 -0
- package/src/guards/policy-resolver.js +3 -3
- package/src/mcp/orphan-guard.js +1 -2
- package/src/mcp/progress.js +4 -3
- package/src/mcp/run-kj.js +1 -0
- package/src/mcp/server-handlers.js +242 -253
- package/src/mcp/server.js +4 -3
- package/src/mcp/tools.js +2 -0
- package/src/orchestrator/agent-fallback.js +1 -3
- package/src/orchestrator/iteration-stages.js +206 -170
- package/src/orchestrator/pre-loop-stages.js +200 -34
- package/src/orchestrator/solomon-rules.js +2 -2
- package/src/orchestrator.js +902 -746
- package/src/planning-game/adapter.js +23 -20
- package/src/planning-game/architect-adrs.js +45 -0
- package/src/planning-game/client.js +15 -1
- package/src/planning-game/decomposition.js +7 -5
- package/src/prompts/architect.js +88 -0
- package/src/prompts/discover.js +54 -53
- package/src/prompts/planner.js +53 -33
- package/src/prompts/triage.js +8 -16
- package/src/review/parser.js +18 -19
- package/src/review/profiles.js +2 -2
- package/src/review/schema.js +3 -3
- package/src/review/scope-filter.js +3 -4
- package/src/roles/architect-role.js +122 -0
- package/src/roles/commiter-role.js +2 -2
- package/src/roles/discover-role.js +59 -67
- package/src/roles/index.js +1 -0
- package/src/roles/planner-role.js +54 -38
- package/src/roles/refactorer-role.js +8 -7
- package/src/roles/researcher-role.js +6 -7
- package/src/roles/reviewer-role.js +4 -5
- package/src/roles/security-role.js +3 -4
- package/src/roles/solomon-role.js +6 -18
- package/src/roles/sonar-role.js +5 -1
- package/src/roles/tester-role.js +8 -5
- package/src/roles/triage-role.js +2 -2
- package/src/session-cleanup.js +29 -24
- package/src/session-store.js +1 -1
- package/src/sonar/api.js +1 -1
- package/src/sonar/manager.js +1 -1
- package/src/sonar/project-key.js +5 -5
- package/src/sonar/scanner.js +34 -65
- package/src/utils/display.js +312 -272
- package/src/utils/git.js +3 -3
- package/src/utils/logger.js +6 -1
- package/src/utils/model-selector.js +5 -5
- package/src/utils/process.js +80 -102
- package/src/utils/rate-limit-detector.js +13 -13
- package/src/utils/run-log.js +55 -52
- package/templates/kj.config.yml +33 -0
- package/templates/roles/architect.md +62 -0
- package/templates/roles/planner.md +1 -0
|
@@ -2,7 +2,7 @@ import { createAgent } from "../agents/index.js";
|
|
|
2
2
|
import { CoderRole } from "../roles/coder-role.js";
|
|
3
3
|
import { RefactorerRole } from "../roles/refactorer-role.js";
|
|
4
4
|
import { SonarRole } from "../roles/sonar-role.js";
|
|
5
|
-
import { addCheckpoint, markSessionStatus, saveSession
|
|
5
|
+
import { addCheckpoint, markSessionStatus, saveSession } from "../session-store.js";
|
|
6
6
|
import { generateDiff, getUntrackedFiles } from "../review/diff-generator.js";
|
|
7
7
|
import { evaluateTddPolicy } from "../review/tdd-policy.js";
|
|
8
8
|
import { validateReviewResult } from "../review/schema.js";
|
|
@@ -195,6 +195,65 @@ export async function runRefactorerStage({ refactorerRole, config, logger, emitt
|
|
|
195
195
|
);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
function handleSolomonAction(solomonResult, session, contextPrefix) {
|
|
199
|
+
if (solomonResult.action === "pause") {
|
|
200
|
+
return { action: "pause", result: { paused: true, sessionId: session.id, question: solomonResult.question, context: `${contextPrefix}_fail_fast` } };
|
|
201
|
+
}
|
|
202
|
+
if (solomonResult.action === "subtask") {
|
|
203
|
+
return { action: "pause", result: { paused: true, sessionId: session.id, subtask: solomonResult.subtask, context: `${contextPrefix}_subtask` } };
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function handleSolomonContinue(solomonResult, session, counterField) {
|
|
209
|
+
if (solomonResult.action !== "continue") return false;
|
|
210
|
+
if (solomonResult.humanGuidance) {
|
|
211
|
+
session.last_reviewer_feedback += `\nUser guidance: ${solomonResult.humanGuidance}`;
|
|
212
|
+
}
|
|
213
|
+
session[counterField] = 0;
|
|
214
|
+
await saveSession(session);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function handleTddFailure({ tddEval, config, logger, emitter, eventBase, session, iteration, askQuestion }) {
|
|
219
|
+
session.last_reviewer_feedback = tddEval.message;
|
|
220
|
+
session.repeated_issue_count += 1;
|
|
221
|
+
await saveSession(session);
|
|
222
|
+
|
|
223
|
+
if (session.repeated_issue_count < config.session.fail_fast_repeats) {
|
|
224
|
+
return { action: "continue" };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
emitProgress(
|
|
228
|
+
emitter,
|
|
229
|
+
makeEvent("solomon:escalate", { ...eventBase, stage: "tdd" }, {
|
|
230
|
+
message: `TDD sub-loop limit reached (${session.repeated_issue_count}/${config.session.fail_fast_repeats})`,
|
|
231
|
+
detail: { subloop: "tdd", retryCount: session.repeated_issue_count, reason: tddEval.reason }
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const solomonResult = await invokeSolomon({
|
|
236
|
+
config, logger, emitter, eventBase, stage: "tdd", askQuestion, session, iteration,
|
|
237
|
+
conflict: {
|
|
238
|
+
stage: "tdd",
|
|
239
|
+
task: session.task,
|
|
240
|
+
iterationCount: session.repeated_issue_count,
|
|
241
|
+
maxIterations: config.session.fail_fast_repeats,
|
|
242
|
+
reason: tddEval.reason,
|
|
243
|
+
sourceFiles: tddEval.sourceFiles,
|
|
244
|
+
testFiles: tddEval.testFiles,
|
|
245
|
+
history: [{ agent: "tdd-policy", feedback: tddEval.message }]
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const actionResult = handleSolomonAction(solomonResult, session, "tdd");
|
|
250
|
+
if (actionResult) return actionResult;
|
|
251
|
+
const continued = await handleSolomonContinue(solomonResult, session, "repeated_issue_count");
|
|
252
|
+
if (continued) return { action: "continue" };
|
|
253
|
+
|
|
254
|
+
return { action: "continue" };
|
|
255
|
+
}
|
|
256
|
+
|
|
198
257
|
export async function runTddCheckStage({ config, logger, emitter, eventBase, session, trackBudget, iteration, askQuestion }) {
|
|
199
258
|
logger.setContext({ iteration, stage: "tdd" });
|
|
200
259
|
const tddDiff = await generateDiff({ baseRef: session.session_start_sha });
|
|
@@ -224,51 +283,75 @@ export async function runTddCheckStage({ config, logger, emitter, eventBase, ses
|
|
|
224
283
|
);
|
|
225
284
|
|
|
226
285
|
if (!tddEval.ok) {
|
|
227
|
-
session
|
|
228
|
-
|
|
229
|
-
await saveSession(session);
|
|
230
|
-
if (session.repeated_issue_count >= config.session.fail_fast_repeats) {
|
|
231
|
-
emitProgress(
|
|
232
|
-
emitter,
|
|
233
|
-
makeEvent("solomon:escalate", { ...eventBase, stage: "tdd" }, {
|
|
234
|
-
message: `TDD sub-loop limit reached (${session.repeated_issue_count}/${config.session.fail_fast_repeats})`,
|
|
235
|
-
detail: { subloop: "tdd", retryCount: session.repeated_issue_count, reason: tddEval.reason }
|
|
236
|
-
})
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
const solomonResult = await invokeSolomon({
|
|
240
|
-
config, logger, emitter, eventBase, stage: "tdd", askQuestion, session, iteration,
|
|
241
|
-
conflict: {
|
|
242
|
-
stage: "tdd",
|
|
243
|
-
task: session.task,
|
|
244
|
-
iterationCount: session.repeated_issue_count,
|
|
245
|
-
maxIterations: config.session.fail_fast_repeats,
|
|
246
|
-
reason: tddEval.reason,
|
|
247
|
-
sourceFiles: tddEval.sourceFiles,
|
|
248
|
-
testFiles: tddEval.testFiles,
|
|
249
|
-
history: [{ agent: "tdd-policy", feedback: tddEval.message }]
|
|
250
|
-
}
|
|
251
|
-
});
|
|
286
|
+
return handleTddFailure({ tddEval, config, logger, emitter, eventBase, session, iteration, askQuestion });
|
|
287
|
+
}
|
|
252
288
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
289
|
+
return { action: "ok" };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function handleSonarStalled({ repeatDetector, logger, emitter, eventBase, session, budgetSummary }) {
|
|
293
|
+
const repeatCounts = repeatDetector.getRepeatCounts();
|
|
294
|
+
const message = `No progress: SonarQube issues repeated ${repeatCounts.sonar} times.`;
|
|
295
|
+
logger.warn(message);
|
|
296
|
+
await markSessionStatus(session, "stalled");
|
|
297
|
+
const repeatState = repeatDetector.isStalled();
|
|
298
|
+
emitProgress(
|
|
299
|
+
emitter,
|
|
300
|
+
makeEvent("session:end", { ...eventBase, stage: "sonar" }, {
|
|
301
|
+
status: "stalled",
|
|
302
|
+
message,
|
|
303
|
+
detail: { reason: repeatState.reason, repeats: repeatCounts.sonar, budget: budgetSummary() }
|
|
304
|
+
})
|
|
305
|
+
);
|
|
306
|
+
return { action: "stalled", result: { approved: false, sessionId: session.id, reason: "stalled" } };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function handleSonarRetryLimit({ config, logger, emitter, eventBase, session, iteration, askQuestion, task, maxSonarRetries, sonarResult }) {
|
|
310
|
+
emitProgress(
|
|
311
|
+
emitter,
|
|
312
|
+
makeEvent("solomon:escalate", { ...eventBase, stage: "sonar" }, {
|
|
313
|
+
message: `Sonar sub-loop limit reached (${session.sonar_retry_count}/${maxSonarRetries})`,
|
|
314
|
+
detail: { subloop: "sonar", retryCount: session.sonar_retry_count, limit: maxSonarRetries, gateStatus: sonarResult.gateStatus }
|
|
315
|
+
})
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const solomonResult = await invokeSolomon({
|
|
319
|
+
config, logger, emitter, eventBase, stage: "sonar", askQuestion, session, iteration,
|
|
320
|
+
conflict: {
|
|
321
|
+
stage: "sonar",
|
|
322
|
+
task,
|
|
323
|
+
iterationCount: session.sonar_retry_count,
|
|
324
|
+
maxIterations: maxSonarRetries,
|
|
325
|
+
history: [{ agent: "sonar", feedback: session.last_sonar_summary }]
|
|
267
326
|
}
|
|
268
|
-
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const actionResult = handleSolomonAction(solomonResult, session, "sonar");
|
|
330
|
+
if (actionResult) return actionResult;
|
|
331
|
+
const continued = await handleSolomonContinue(solomonResult, session, "sonar_retry_count");
|
|
332
|
+
if (continued) return { action: "continue" };
|
|
333
|
+
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function handleSonarBlocking({ sonarResult, config, logger, emitter, eventBase, session, iteration, repeatDetector, budgetSummary, askQuestion, task }) {
|
|
338
|
+
repeatDetector.addIteration(sonarResult.issues, []);
|
|
339
|
+
const repeatState = repeatDetector.isStalled();
|
|
340
|
+
if (repeatState.stalled) {
|
|
341
|
+
return handleSonarStalled({ repeatDetector, logger, emitter, eventBase, session, budgetSummary });
|
|
269
342
|
}
|
|
270
343
|
|
|
271
|
-
|
|
344
|
+
session.last_reviewer_feedback = `Sonar gate blocking (${sonarResult.gateStatus}). Resolve critical findings first.`;
|
|
345
|
+
session.sonar_retry_count = (session.sonar_retry_count || 0) + 1;
|
|
346
|
+
await saveSession(session);
|
|
347
|
+
const maxSonarRetries = config.session.max_sonar_retries ?? config.session.fail_fast_repeats;
|
|
348
|
+
|
|
349
|
+
if (session.sonar_retry_count >= maxSonarRetries) {
|
|
350
|
+
const result = await handleSonarRetryLimit({ config, logger, emitter, eventBase, session, iteration, askQuestion, task, maxSonarRetries, sonarResult });
|
|
351
|
+
if (result) return result;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return { action: "continue" };
|
|
272
355
|
}
|
|
273
356
|
|
|
274
357
|
export async function runSonarStage({ config, logger, emitter, eventBase, session, trackBudget, iteration, repeatDetector, budgetSummary, sonarState, askQuestion, task }) {
|
|
@@ -326,64 +409,7 @@ export async function runSonarStage({ config, logger, emitter, eventBase, sessio
|
|
|
326
409
|
);
|
|
327
410
|
|
|
328
411
|
if (sonarResult.blocking) {
|
|
329
|
-
|
|
330
|
-
const repeatState = repeatDetector.isStalled();
|
|
331
|
-
if (repeatState.stalled) {
|
|
332
|
-
const repeatCounts = repeatDetector.getRepeatCounts();
|
|
333
|
-
const message = `No progress: SonarQube issues repeated ${repeatCounts.sonar} times.`;
|
|
334
|
-
logger.warn(message);
|
|
335
|
-
await markSessionStatus(session, "stalled");
|
|
336
|
-
emitProgress(
|
|
337
|
-
emitter,
|
|
338
|
-
makeEvent("session:end", { ...eventBase, stage: "sonar" }, {
|
|
339
|
-
status: "stalled",
|
|
340
|
-
message,
|
|
341
|
-
detail: { reason: repeatState.reason, repeats: repeatCounts.sonar, budget: budgetSummary() }
|
|
342
|
-
})
|
|
343
|
-
);
|
|
344
|
-
return { action: "stalled", result: { approved: false, sessionId: session.id, reason: "stalled" } };
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
session.last_reviewer_feedback = `Sonar gate blocking (${sonarResult.gateStatus}). Resolve critical findings first.`;
|
|
348
|
-
session.sonar_retry_count = (session.sonar_retry_count || 0) + 1;
|
|
349
|
-
await saveSession(session);
|
|
350
|
-
const maxSonarRetries = config.session.max_sonar_retries ?? config.session.fail_fast_repeats;
|
|
351
|
-
if (session.sonar_retry_count >= maxSonarRetries) {
|
|
352
|
-
emitProgress(
|
|
353
|
-
emitter,
|
|
354
|
-
makeEvent("solomon:escalate", { ...eventBase, stage: "sonar" }, {
|
|
355
|
-
message: `Sonar sub-loop limit reached (${session.sonar_retry_count}/${maxSonarRetries})`,
|
|
356
|
-
detail: { subloop: "sonar", retryCount: session.sonar_retry_count, limit: maxSonarRetries, gateStatus: sonarResult.gateStatus }
|
|
357
|
-
})
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
const solomonResult = await invokeSolomon({
|
|
361
|
-
config, logger, emitter, eventBase, stage: "sonar", askQuestion, session, iteration,
|
|
362
|
-
conflict: {
|
|
363
|
-
stage: "sonar",
|
|
364
|
-
task,
|
|
365
|
-
iterationCount: session.sonar_retry_count,
|
|
366
|
-
maxIterations: maxSonarRetries,
|
|
367
|
-
history: [{ agent: "sonar", feedback: session.last_sonar_summary }]
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
if (solomonResult.action === "pause") {
|
|
372
|
-
return { action: "pause", result: { paused: true, sessionId: session.id, question: solomonResult.question, context: "sonar_fail_fast" } };
|
|
373
|
-
}
|
|
374
|
-
if (solomonResult.action === "continue") {
|
|
375
|
-
if (solomonResult.humanGuidance) {
|
|
376
|
-
session.last_reviewer_feedback += `\nUser guidance: ${solomonResult.humanGuidance}`;
|
|
377
|
-
}
|
|
378
|
-
session.sonar_retry_count = 0;
|
|
379
|
-
await saveSession(session);
|
|
380
|
-
return { action: "continue" };
|
|
381
|
-
}
|
|
382
|
-
if (solomonResult.action === "subtask") {
|
|
383
|
-
return { action: "pause", result: { paused: true, sessionId: session.id, subtask: solomonResult.subtask, context: "sonar_subtask" } };
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return { action: "continue" };
|
|
412
|
+
return handleSonarBlocking({ sonarResult, config, logger, emitter, eventBase, session, iteration, repeatDetector, budgetSummary, askQuestion, task });
|
|
387
413
|
}
|
|
388
414
|
|
|
389
415
|
// Sonar passed — reset retry counter
|
|
@@ -401,6 +427,78 @@ export async function runSonarStage({ config, logger, emitter, eventBase, sessio
|
|
|
401
427
|
return { action: "ok", stageResult };
|
|
402
428
|
}
|
|
403
429
|
|
|
430
|
+
async function handleReviewerStalledSolomon({ review, repeatCounts, repeatState, config, logger, emitter, eventBase, session, iteration, task, askQuestion, budgetSummary, repeatDetector }) {
|
|
431
|
+
logger.warn(`Reviewer stalled (${repeatCounts.reviewer} repeats). Invoking Solomon mediation.`);
|
|
432
|
+
emitProgress(
|
|
433
|
+
emitter,
|
|
434
|
+
makeEvent("solomon:escalate", { ...eventBase, stage: "reviewer" }, {
|
|
435
|
+
message: `Reviewer stalled — Solomon mediating`,
|
|
436
|
+
detail: { repeats: repeatCounts.reviewer, reason: repeatState.reason }
|
|
437
|
+
})
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
const solomonResult = await invokeSolomon({
|
|
441
|
+
config, logger, emitter, eventBase, stage: "reviewer", askQuestion, session, iteration,
|
|
442
|
+
conflict: {
|
|
443
|
+
stage: "reviewer",
|
|
444
|
+
task,
|
|
445
|
+
iterationCount: repeatCounts.reviewer,
|
|
446
|
+
maxIterations: config.session?.fail_fast_repeats ?? 2,
|
|
447
|
+
stalledReason: repeatState.reason,
|
|
448
|
+
blockingIssues: review.blocking_issues,
|
|
449
|
+
history: [{ agent: "reviewer", feedback: review.blocking_issues.map(x => x.description).join("; ") }]
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
if (solomonResult.action === "pause") {
|
|
454
|
+
await markSessionStatus(session, "stalled");
|
|
455
|
+
return { review, stalled: true, stalledResult: { paused: true, sessionId: session.id, question: solomonResult.question, context: "reviewer_stalled" } };
|
|
456
|
+
}
|
|
457
|
+
if (solomonResult.action === "continue") {
|
|
458
|
+
repeatDetector.reviewer = { lastHash: null, repeatCount: 0 };
|
|
459
|
+
if (solomonResult.humanGuidance) {
|
|
460
|
+
session.last_reviewer_feedback = `Solomon/user guidance: ${solomonResult.humanGuidance}`;
|
|
461
|
+
await saveSession(session);
|
|
462
|
+
}
|
|
463
|
+
return { review };
|
|
464
|
+
}
|
|
465
|
+
if (solomonResult.action === "subtask") {
|
|
466
|
+
return { review, stalled: true, stalledResult: { paused: true, sessionId: session.id, subtask: solomonResult.subtask, context: "reviewer_subtask" } };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Fallback
|
|
470
|
+
const message = `Manual intervention required: reviewer issues repeated ${repeatCounts.reviewer} times.`;
|
|
471
|
+
await markSessionStatus(session, "stalled");
|
|
472
|
+
emitProgress(
|
|
473
|
+
emitter,
|
|
474
|
+
makeEvent("session:end", { ...eventBase, stage: "reviewer" }, {
|
|
475
|
+
status: "stalled",
|
|
476
|
+
message,
|
|
477
|
+
detail: { reason: repeatState.reason, repeats: repeatCounts.reviewer, budget: budgetSummary() }
|
|
478
|
+
})
|
|
479
|
+
);
|
|
480
|
+
return { review, stalled: true, stalledResult: { approved: false, sessionId: session.id, reason: "stalled" } };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function handleReviewerRejection({ review, repeatDetector, config, logger, emitter, eventBase, session, iteration, task, askQuestion, budgetSummary }) {
|
|
484
|
+
repeatDetector.addIteration([], review.blocking_issues);
|
|
485
|
+
const repeatState = repeatDetector.isStalled();
|
|
486
|
+
if (!repeatState.stalled) return null;
|
|
487
|
+
|
|
488
|
+
const repeatCounts = repeatDetector.getRepeatCounts();
|
|
489
|
+
return handleReviewerStalledSolomon({ review, repeatCounts, repeatState, config, logger, emitter, eventBase, session, iteration, task, askQuestion, budgetSummary, repeatDetector });
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function fetchReviewDiff(session, logger) {
|
|
493
|
+
if (session.becaria_pr_number) {
|
|
494
|
+
const { getPrDiff } = await import("../becaria/pr-diff.js");
|
|
495
|
+
const diff = await getPrDiff(session.becaria_pr_number);
|
|
496
|
+
logger.info(`Reviewer reading PR diff #${session.becaria_pr_number}`);
|
|
497
|
+
return diff;
|
|
498
|
+
}
|
|
499
|
+
return generateDiff({ baseRef: session.session_start_sha });
|
|
500
|
+
}
|
|
501
|
+
|
|
404
502
|
export async function runReviewerStage({ reviewerRole, config, logger, emitter, eventBase, session, trackBudget, iteration, reviewRules, task, repeatDetector, budgetSummary, askQuestion }) {
|
|
405
503
|
logger.setContext({ iteration, stage: "reviewer" });
|
|
406
504
|
emitProgress(
|
|
@@ -411,14 +509,7 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
411
509
|
})
|
|
412
510
|
);
|
|
413
511
|
|
|
414
|
-
|
|
415
|
-
if (session.becaria_pr_number) {
|
|
416
|
-
const { getPrDiff } = await import("../becaria/pr-diff.js");
|
|
417
|
-
diff = await getPrDiff(session.becaria_pr_number);
|
|
418
|
-
logger.info(`Reviewer reading PR diff #${session.becaria_pr_number}`);
|
|
419
|
-
} else {
|
|
420
|
-
diff = await generateDiff({ baseRef: session.session_start_sha });
|
|
421
|
-
}
|
|
512
|
+
const diff = await fetchReviewDiff(session, logger);
|
|
422
513
|
const reviewerOnOutput = ({ stream, line }) => {
|
|
423
514
|
emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "reviewer" }, {
|
|
424
515
|
message: line,
|
|
@@ -447,7 +538,7 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
447
538
|
reviewerStall.stop();
|
|
448
539
|
}
|
|
449
540
|
|
|
450
|
-
if (!reviewerExec.execResult
|
|
541
|
+
if (!reviewerExec.execResult?.ok) {
|
|
451
542
|
const lastAttempt = reviewerExec.attempts.at(-1);
|
|
452
543
|
const details =
|
|
453
544
|
lastAttempt?.result?.error ||
|
|
@@ -493,17 +584,17 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
493
584
|
summary: reviewResult.raw_summary || "",
|
|
494
585
|
confidence: reviewResult.confidence ?? 0
|
|
495
586
|
});
|
|
496
|
-
} catch (
|
|
497
|
-
logger.warn(`Reviewer output validation failed: ${
|
|
587
|
+
} catch (error_) {
|
|
588
|
+
logger.warn(`Reviewer output validation failed: ${error_.message}`);
|
|
498
589
|
review = {
|
|
499
590
|
approved: false,
|
|
500
591
|
blocking_issues: [{
|
|
501
592
|
id: "PARSE_ERROR",
|
|
502
593
|
severity: "high",
|
|
503
|
-
description: `Reviewer output could not be parsed: ${
|
|
594
|
+
description: `Reviewer output could not be parsed: ${error_.message}`
|
|
504
595
|
}],
|
|
505
596
|
non_blocking_suggestions: [],
|
|
506
|
-
summary: `Parse error: ${
|
|
597
|
+
summary: `Parse error: ${error_.message}`,
|
|
507
598
|
confidence: 0
|
|
508
599
|
};
|
|
509
600
|
}
|
|
@@ -565,63 +656,8 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
565
656
|
);
|
|
566
657
|
|
|
567
658
|
if (!review.approved) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (repeatState.stalled) {
|
|
571
|
-
const repeatCounts = repeatDetector.getRepeatCounts();
|
|
572
|
-
|
|
573
|
-
// --- Solomon mediation for stalled reviewer ---
|
|
574
|
-
logger.warn(`Reviewer stalled (${repeatCounts.reviewer} repeats). Invoking Solomon mediation.`);
|
|
575
|
-
emitProgress(
|
|
576
|
-
emitter,
|
|
577
|
-
makeEvent("solomon:escalate", { ...eventBase, stage: "reviewer" }, {
|
|
578
|
-
message: `Reviewer stalled — Solomon mediating`,
|
|
579
|
-
detail: { repeats: repeatCounts.reviewer, reason: repeatState.reason }
|
|
580
|
-
})
|
|
581
|
-
);
|
|
582
|
-
|
|
583
|
-
const solomonResult = await invokeSolomon({
|
|
584
|
-
config, logger, emitter, eventBase, stage: "reviewer", askQuestion, session, iteration,
|
|
585
|
-
conflict: {
|
|
586
|
-
stage: "reviewer",
|
|
587
|
-
task,
|
|
588
|
-
iterationCount: repeatCounts.reviewer,
|
|
589
|
-
maxIterations: config.session?.fail_fast_repeats ?? 2,
|
|
590
|
-
stalledReason: repeatState.reason,
|
|
591
|
-
blockingIssues: review.blocking_issues,
|
|
592
|
-
history: [{ agent: "reviewer", feedback: review.blocking_issues.map(x => x.description).join("; ") }]
|
|
593
|
-
}
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
if (solomonResult.action === "pause") {
|
|
597
|
-
await markSessionStatus(session, "stalled");
|
|
598
|
-
return { review, stalled: true, stalledResult: { paused: true, sessionId: session.id, question: solomonResult.question, context: "reviewer_stalled" } };
|
|
599
|
-
}
|
|
600
|
-
if (solomonResult.action === "continue") {
|
|
601
|
-
repeatDetector.reviewer = { lastHash: null, repeatCount: 0 };
|
|
602
|
-
if (solomonResult.humanGuidance) {
|
|
603
|
-
session.last_reviewer_feedback = `Solomon/user guidance: ${solomonResult.humanGuidance}`;
|
|
604
|
-
await saveSession(session);
|
|
605
|
-
}
|
|
606
|
-
return { review };
|
|
607
|
-
}
|
|
608
|
-
if (solomonResult.action === "subtask") {
|
|
609
|
-
return { review, stalled: true, stalledResult: { paused: true, sessionId: session.id, subtask: solomonResult.subtask, context: "reviewer_subtask" } };
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// Fallback
|
|
613
|
-
const message = `Manual intervention required: reviewer issues repeated ${repeatCounts.reviewer} times.`;
|
|
614
|
-
await markSessionStatus(session, "stalled");
|
|
615
|
-
emitProgress(
|
|
616
|
-
emitter,
|
|
617
|
-
makeEvent("session:end", { ...eventBase, stage: "reviewer" }, {
|
|
618
|
-
status: "stalled",
|
|
619
|
-
message,
|
|
620
|
-
detail: { reason: repeatState.reason, repeats: repeatCounts.reviewer, budget: budgetSummary() }
|
|
621
|
-
})
|
|
622
|
-
);
|
|
623
|
-
return { review, stalled: true, stalledResult: { approved: false, sessionId: session.id, reason: "stalled" } };
|
|
624
|
-
}
|
|
659
|
+
const rejectionResult = await handleReviewerRejection({ review, repeatDetector, config, logger, emitter, eventBase, session, iteration, task, askQuestion, budgetSummary });
|
|
660
|
+
if (rejectionResult) return rejectionResult;
|
|
625
661
|
}
|
|
626
662
|
|
|
627
663
|
return { review };
|