@magnusekdahl/parallix 1.3.3 → 1.3.4
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/docs/agents.md +1 -1
- package/docs/use-cases.md +1 -1
- package/lib/agents/agents.js +20 -3
- package/lib/agents/agents.ts +14 -3
- package/lib/agents/mistral.js +128 -14
- package/lib/agents/mistral.ts +145 -5
- package/lib/commands/active.js +69 -39
- package/lib/commands/active.ts +97 -60
- package/lib/commands/config.ts +3 -3
- package/lib/commands/coverage-gate.ts +1 -1
- package/lib/commands/draft.js +1 -1
- package/lib/commands/draft.ts +1 -1
- package/lib/commands/handoff.js +13 -8
- package/lib/commands/handoff.ts +24 -18
- package/lib/commands/rebase.js +1 -1
- package/lib/commands/rebase.ts +2 -2
- package/lib/commands/repair-handoff.js +141 -20
- package/lib/commands/repair-handoff.ts +185 -45
- package/lib/commands/resolve-conflict.js +1 -1
- package/lib/commands/resolve-conflict.ts +2 -2
- package/lib/commands/stats-backfill.ts +10 -10
- package/lib/commands/stats.js +38 -11
- package/lib/commands/stats.ts +40 -96
- package/lib/core/fmt.ts +2 -2
- package/lib/core/git.ts +2 -2
- package/lib/core/gitignore.ts +2 -2
- package/lib/core/mission-utils.js +2 -2
- package/lib/core/mission-utils.ts +2 -2
- package/lib/core/persistent-data-migration.ts +2 -2
- package/lib/core/spawn-tee.ts +1 -1
- package/lib/core/state-map.ts +2 -2
- package/lib/core/storage.ts +1 -1
- package/lib/core/verification.ts +1 -1
- package/lib/review/rebase.ts +12 -12
- package/lib/review/review-artifacts.ts +35 -35
- package/lib/review/review-commands.ts +40 -40
- package/lib/review/review-events.ts +12 -12
- package/lib/review/review-loop.js +236 -7
- package/lib/review/review-loop.ts +338 -22
- package/lib/review/review-polling.ts +6 -6
- package/lib/review/review-prompts.js +8 -4
- package/lib/review/review-prompts.ts +12 -8
- package/lib/review/review-state.js +1 -1
- package/lib/review/review-state.ts +2 -2
- package/package.json +3 -2
- package/prompts/review-verbose.md +1 -1
- package/prompts/review.md +1 -1
|
@@ -51,7 +51,7 @@ export function readTextFlag(
|
|
|
51
51
|
inlineFlag: string,
|
|
52
52
|
fileFlag: string,
|
|
53
53
|
label: string,
|
|
54
|
-
options: { readFileSync?: typeof fs.readFileSync; error?: (
|
|
54
|
+
options: { readFileSync?: typeof fs.readFileSync; error?: (_msg: string) => void; exit?: (_code: number) => never } = {}
|
|
55
55
|
): string | null {
|
|
56
56
|
const readFileSync = options.readFileSync || fs.readFileSync;
|
|
57
57
|
const error = options.error || fmt.log.plainError;
|
|
@@ -98,8 +98,8 @@ export function formatStaticReviewSuccess(slug: string): string {
|
|
|
98
98
|
function repairStaleActiveTaskAfterReview(
|
|
99
99
|
slug: string,
|
|
100
100
|
options: {
|
|
101
|
-
log?: (
|
|
102
|
-
error?: (
|
|
101
|
+
log?: (_msg: string) => void;
|
|
102
|
+
error?: (_msg: string) => void;
|
|
103
103
|
getTaskStatusFn?: typeof getTaskStatus;
|
|
104
104
|
resolveTaskFileFn?: typeof resolveTaskFile;
|
|
105
105
|
transitionTaskFn?: typeof transitionTask;
|
|
@@ -133,7 +133,7 @@ function repairStaleActiveTaskAfterReview(
|
|
|
133
133
|
|
|
134
134
|
async function commitPersistedReviewOutputs(
|
|
135
135
|
slug: string,
|
|
136
|
-
options: { worktree?: string; taskFile?: string | null; log?: (
|
|
136
|
+
options: { worktree?: string; taskFile?: string | null; log?: (_msg: string) => void; error?: (_msg: string) => void } = {}
|
|
137
137
|
): Promise<{ ok: boolean; dirty?: boolean; unsafe?: boolean }> {
|
|
138
138
|
return commitSafeMissionArtifacts(slug, options.worktree || process.cwd(), {
|
|
139
139
|
taskFile: options.taskFile || null,
|
|
@@ -146,8 +146,8 @@ export function postStaticReviewComment(
|
|
|
146
146
|
slug: string,
|
|
147
147
|
message: string,
|
|
148
148
|
options: {
|
|
149
|
-
log?: (
|
|
150
|
-
error?: (
|
|
149
|
+
log?: (_msg: string) => void;
|
|
150
|
+
error?: (_msg: string) => void;
|
|
151
151
|
resolveTaskFileFn?: typeof resolveTaskFile;
|
|
152
152
|
getTaskImplementerFn?: typeof getTaskImplementer;
|
|
153
153
|
resolveWorktreeFn?: typeof resolveWorktree;
|
|
@@ -215,7 +215,7 @@ export function postStaticReviewComment(
|
|
|
215
215
|
export function performStaticReview(
|
|
216
216
|
slug: string,
|
|
217
217
|
options: {
|
|
218
|
-
log?: (
|
|
218
|
+
log?: (_msg: string) => void;
|
|
219
219
|
findMissionDir?: typeof findMissionDir;
|
|
220
220
|
findCheckpoints?: typeof findCheckpoints;
|
|
221
221
|
readFileSync?: typeof fs.readFileSync;
|
|
@@ -338,9 +338,9 @@ export function verifyReview(
|
|
|
338
338
|
slug: string,
|
|
339
339
|
skipGate: boolean | string,
|
|
340
340
|
options: {
|
|
341
|
-
log?: (
|
|
342
|
-
error?: (
|
|
343
|
-
exit?: (
|
|
341
|
+
log?: (_msg: string) => void;
|
|
342
|
+
error?: (_msg: string) => void;
|
|
343
|
+
exit?: (_code: number) => never;
|
|
344
344
|
resolveWorktreeFn?: typeof resolveWorktree;
|
|
345
345
|
findMissionDirFn?: typeof findMissionDir;
|
|
346
346
|
getCurrentBranchFn?: typeof getCurrentBranch;
|
|
@@ -522,15 +522,15 @@ export async function submitForReview(
|
|
|
522
522
|
slug: string,
|
|
523
523
|
skipGate: boolean | string,
|
|
524
524
|
options: {
|
|
525
|
-
exit?: (
|
|
525
|
+
exit?: (_code: number) => never;
|
|
526
526
|
resolveTaskFileFn?: typeof resolveTaskFile;
|
|
527
527
|
getTaskImplementerFn?: typeof getTaskImplementer;
|
|
528
528
|
resolveWorktreeFn?: typeof resolveWorktree;
|
|
529
|
-
performHandoffFn?: (
|
|
529
|
+
performHandoffFn?: (_slug: string, _opts?: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
530
530
|
readReviewStateFn?: typeof readReviewState;
|
|
531
531
|
isReviewProviderEnabledFn?: typeof isProviderEnabled;
|
|
532
532
|
isForgejoReviewEnabledFn?: typeof isProviderEnabled;
|
|
533
|
-
log?: (
|
|
533
|
+
log?: (_msg: string) => void;
|
|
534
534
|
} = {}
|
|
535
535
|
): Promise<void> {
|
|
536
536
|
const exit = options.exit || process.exit;
|
|
@@ -583,9 +583,9 @@ export async function submitForReview(
|
|
|
583
583
|
export async function readComments(
|
|
584
584
|
slug: string,
|
|
585
585
|
options: {
|
|
586
|
-
log?: (
|
|
587
|
-
error?: (
|
|
588
|
-
exit?: (
|
|
586
|
+
log?: (_msg: string) => void;
|
|
587
|
+
error?: (_msg: string) => void;
|
|
588
|
+
exit?: (_code: number) => never;
|
|
589
589
|
readTokenFn?: typeof readToken;
|
|
590
590
|
readReviewStateFn?: typeof readReviewState;
|
|
591
591
|
resolveReviewUserFn?: typeof resolveReviewUser;
|
|
@@ -676,9 +676,9 @@ export async function readComments(
|
|
|
676
676
|
export async function pushRound(
|
|
677
677
|
slug: string,
|
|
678
678
|
options: {
|
|
679
|
-
log?: (
|
|
680
|
-
error?: (
|
|
681
|
-
exit?: (
|
|
679
|
+
log?: (_msg: string) => void;
|
|
680
|
+
error?: (_msg: string) => void;
|
|
681
|
+
exit?: (_code: number) => never;
|
|
682
682
|
resolveWorktreeFn?: typeof resolveWorktree;
|
|
683
683
|
resolveTaskFileFn?: typeof resolveTaskFile;
|
|
684
684
|
getTaskImplementerFn?: typeof getTaskImplementer;
|
|
@@ -800,7 +800,7 @@ export async function pushRound(
|
|
|
800
800
|
export function showReviewStatus(
|
|
801
801
|
slug: string,
|
|
802
802
|
options: {
|
|
803
|
-
log?: (
|
|
803
|
+
log?: (_msg: string) => void;
|
|
804
804
|
readReviewStateFn?: typeof readReviewState;
|
|
805
805
|
resolveWorktreeFn?: typeof resolveWorktree;
|
|
806
806
|
} = {}
|
|
@@ -843,9 +843,9 @@ export function commentRound(
|
|
|
843
843
|
slug: string,
|
|
844
844
|
message: string,
|
|
845
845
|
options: {
|
|
846
|
-
log?: (
|
|
847
|
-
error?: (
|
|
848
|
-
exit?: (
|
|
846
|
+
log?: (_msg: string) => void;
|
|
847
|
+
error?: (_msg: string) => void;
|
|
848
|
+
exit?: (_code: number) => never;
|
|
849
849
|
readReviewStateFn?: typeof readReviewState;
|
|
850
850
|
writeReviewStateFn?: typeof writeReviewState;
|
|
851
851
|
resolveReviewUserFn?: typeof resolveReviewUser;
|
|
@@ -902,8 +902,8 @@ export function commentRound(
|
|
|
902
902
|
export async function consumeArtifacts(
|
|
903
903
|
slug: string,
|
|
904
904
|
options: {
|
|
905
|
-
log?: (
|
|
906
|
-
error?: (
|
|
905
|
+
log?: (_msg: string) => void;
|
|
906
|
+
error?: (_msg: string) => void;
|
|
907
907
|
resolveWorktreeFn?: typeof resolveWorktree;
|
|
908
908
|
transitionTaskFn?: typeof transitionTask;
|
|
909
909
|
consumeReviewerArtifactsFn?: typeof consumeReviewerArtifacts;
|
|
@@ -1015,9 +1015,9 @@ export function submitReviewRound(
|
|
|
1015
1015
|
outcome: string,
|
|
1016
1016
|
message: string,
|
|
1017
1017
|
options: {
|
|
1018
|
-
log?: (
|
|
1019
|
-
error?: (
|
|
1020
|
-
exit?: (
|
|
1018
|
+
log?: (_msg: string) => void;
|
|
1019
|
+
error?: (_msg: string) => void;
|
|
1020
|
+
exit?: (_code: number) => never;
|
|
1021
1021
|
transitionTaskFn?: typeof transitionTask;
|
|
1022
1022
|
readReviewStateFn?: typeof readReviewState;
|
|
1023
1023
|
writeReviewStateFn?: typeof writeReviewState;
|
|
@@ -1178,9 +1178,9 @@ export function submitReviewRound(
|
|
|
1178
1178
|
export async function closeMissionPr(
|
|
1179
1179
|
slug: string,
|
|
1180
1180
|
options: {
|
|
1181
|
-
log?: (
|
|
1182
|
-
error?: (
|
|
1183
|
-
exit?: (
|
|
1181
|
+
log?: (_msg: string) => void;
|
|
1182
|
+
error?: (_msg: string) => void;
|
|
1183
|
+
exit?: (_code: number) => never;
|
|
1184
1184
|
readTokenFn?: typeof readToken;
|
|
1185
1185
|
readReviewStateFn?: typeof readReviewState;
|
|
1186
1186
|
resolveReviewUserFn?: typeof resolveReviewUser;
|
|
@@ -1233,9 +1233,9 @@ export function createEventHandler(
|
|
|
1233
1233
|
slug: string,
|
|
1234
1234
|
args: string[],
|
|
1235
1235
|
options: {
|
|
1236
|
-
log?: (
|
|
1237
|
-
error?: (
|
|
1238
|
-
exit?: (
|
|
1236
|
+
log?: (_msg: string) => void;
|
|
1237
|
+
error?: (_msg: string) => void;
|
|
1238
|
+
exit?: (_code: number) => never;
|
|
1239
1239
|
resolveWorktreeFn?: typeof resolveWorktree;
|
|
1240
1240
|
readReviewStateFn?: typeof readReviewState;
|
|
1241
1241
|
} = {}
|
|
@@ -1349,9 +1349,9 @@ export function importLegacyHandler(
|
|
|
1349
1349
|
slug: string,
|
|
1350
1350
|
args: string[],
|
|
1351
1351
|
options: {
|
|
1352
|
-
log?: (
|
|
1353
|
-
error?: (
|
|
1354
|
-
exit?: (
|
|
1352
|
+
log?: (_msg: string) => void;
|
|
1353
|
+
error?: (_msg: string) => void;
|
|
1354
|
+
exit?: (_code: number) => never;
|
|
1355
1355
|
resolveWorktreeFn?: typeof resolveWorktree;
|
|
1356
1356
|
} = {}
|
|
1357
1357
|
): void {
|
|
@@ -1383,9 +1383,9 @@ export async function review(
|
|
|
1383
1383
|
args: string[],
|
|
1384
1384
|
options: {
|
|
1385
1385
|
inferSlugFn?: typeof inferSlug;
|
|
1386
|
-
log?: (
|
|
1387
|
-
error?: (
|
|
1388
|
-
exit?: (
|
|
1386
|
+
log?: (_msg: string) => void;
|
|
1387
|
+
error?: (_msg: string) => void;
|
|
1388
|
+
exit?: (_code: number) => never;
|
|
1389
1389
|
verifyReviewFn?: typeof verifyReview;
|
|
1390
1390
|
submitForReviewFn?: typeof submitForReview;
|
|
1391
1391
|
consumeArtifactsFn?: typeof consumeArtifacts;
|
|
@@ -207,8 +207,8 @@ export interface CreateEventParams {
|
|
|
207
207
|
export interface CreateEventOptions {
|
|
208
208
|
skipGit?: boolean;
|
|
209
209
|
gitFn?: typeof git;
|
|
210
|
-
log?: (
|
|
211
|
-
error?: (
|
|
210
|
+
log?: (_msg: string) => void;
|
|
211
|
+
error?: (_msg: string) => void;
|
|
212
212
|
worktree?: string;
|
|
213
213
|
allowMissingRequiredFields?: boolean;
|
|
214
214
|
}
|
|
@@ -247,7 +247,7 @@ export interface ReadAllEventsOptions {
|
|
|
247
247
|
rootDir?: string;
|
|
248
248
|
readdirSync?: typeof fs.readdirSync;
|
|
249
249
|
readFileSync?: typeof fs.readFileSync;
|
|
250
|
-
error?: (
|
|
250
|
+
error?: (_msg: string) => void;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
export interface ParseEventResult {
|
|
@@ -352,14 +352,14 @@ function classifyComment(comment: { body?: string }): string | null {
|
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
export interface ConsumeHumanNotesOptions {
|
|
355
|
-
getCommentsFn?: (
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
getCommentsFn?: (_branch: string, _token: string) => Promise<unknown[]>;
|
|
356
|
+
createEventFn?: typeof createEvent;
|
|
357
|
+
readTokenFn?: (_user: string) => string | null;
|
|
358
358
|
reviewIdentity?: string | null;
|
|
359
359
|
forgejoUser?: string | null;
|
|
360
360
|
worktree?: string;
|
|
361
|
-
log?: (
|
|
362
|
-
error?: (
|
|
361
|
+
log?: (_msg: string) => void;
|
|
362
|
+
error?: (_msg: string) => void;
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
/**
|
|
@@ -578,8 +578,8 @@ function createEvent(slug: string, eventType: string, params: CreateEventParams,
|
|
|
578
578
|
|
|
579
579
|
export interface ImportLegacyArtifactOptions {
|
|
580
580
|
tmpDir?: string;
|
|
581
|
-
log?: (
|
|
582
|
-
error?: (
|
|
581
|
+
log?: (_msg: string) => void;
|
|
582
|
+
error?: (_msg: string) => void;
|
|
583
583
|
worktree?: string;
|
|
584
584
|
}
|
|
585
585
|
|
|
@@ -650,9 +650,9 @@ const LEGACY_ARTIFACT_TO_EVENT_TYPE: Readonly<Record<string, string>> = Object.f
|
|
|
650
650
|
|
|
651
651
|
export interface ImportAllLegacyOptions {
|
|
652
652
|
tmpDir?: string;
|
|
653
|
-
error?: (
|
|
653
|
+
error?: (_msg: string) => void;
|
|
654
654
|
worktree?: string;
|
|
655
|
-
log?: (
|
|
655
|
+
log?: (_msg: string) => void;
|
|
656
656
|
}
|
|
657
657
|
|
|
658
658
|
/**
|
|
@@ -44,12 +44,16 @@ exports.maybeUpdateGraphifyBeforeReview = maybeUpdateGraphifyBeforeReview;
|
|
|
44
44
|
exports.applyAgentFallback = applyAgentFallback;
|
|
45
45
|
exports.persistNormalizedPhaseRepair = persistNormalizedPhaseRepair;
|
|
46
46
|
exports.stageLaunchSinceMs = stageLaunchSinceMs;
|
|
47
|
+
exports.classifyGateFailure = classifyGateFailure;
|
|
48
|
+
exports.runPreReviewGate = runPreReviewGate;
|
|
49
|
+
exports.handleGateFailureAutoBounce = handleGateFailureAutoBounce;
|
|
47
50
|
exports.startReviewLoop = startReviewLoop;
|
|
48
51
|
const fs = __importStar(require("fs"));
|
|
49
52
|
const path = __importStar(require("path"));
|
|
50
53
|
const fmt = __importStar(require("../core/fmt.js"));
|
|
51
54
|
const git_js_1 = require("../core/git.js");
|
|
52
55
|
const mission_utils_js_1 = require("../core/mission-utils.js");
|
|
56
|
+
const verification_js_1 = require("../core/verification.js");
|
|
53
57
|
const backlog_js_1 = require("../tools/backlog.js");
|
|
54
58
|
const state_map_js_1 = require("../core/state-map.js");
|
|
55
59
|
const review_adapter_js_1 = require("./review-adapter.js");
|
|
@@ -232,10 +236,164 @@ function stageLaunchSinceMs(result) {
|
|
|
232
236
|
return Number.isFinite(startedMs) ? startedMs : 0;
|
|
233
237
|
}
|
|
234
238
|
// ============================================================================
|
|
239
|
+
// Pre-review Gate Enforcement (ADR 0048 Control C1 / TASK-1385)
|
|
240
|
+
// ============================================================================
|
|
241
|
+
// Minimal error classifier stub for gate failures (TASK-1389 dependency).
|
|
242
|
+
// Maps gate failures to Class 6: genuine gate failure — code issue, dispatch: auto-send-back.
|
|
243
|
+
// TASK-1389 will replace this with the full 8-class dispatch table.
|
|
244
|
+
const GATE_FAILURE_CLASS = 'class-6-genuine-gate-failure';
|
|
245
|
+
const GATE_FAILURE_ACTION = 'auto-send-back';
|
|
246
|
+
function classifyGateFailure(_output) {
|
|
247
|
+
// Gate failures are genuine code issues — always relaunchable via auto-send-back.
|
|
248
|
+
// The full classifier (TASK-1389) will expand this to 8 classes with nuanced dispatch.
|
|
249
|
+
return {
|
|
250
|
+
classification: GATE_FAILURE_CLASS,
|
|
251
|
+
action: GATE_FAILURE_ACTION,
|
|
252
|
+
isRelaunchable: true,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Run the verification gate with the mission area before a review round.
|
|
257
|
+
* Captures stdout/stderr for use in auto-bounce fix prompts.
|
|
258
|
+
*/
|
|
259
|
+
async function runPreReviewGate(slug, worktree, opts = {}) {
|
|
260
|
+
const { findMissionAreaFn = mission_utils_js_1.findMissionArea, runFn: runFnOverride = git_js_1.run, log = fmt.log.plain, error = fmt.log.plainError, } = opts;
|
|
261
|
+
const missionDir = (0, mission_utils_js_1.findMissionDir)(slug, worktree);
|
|
262
|
+
const area = missionDir ? findMissionAreaFn(missionDir) : 'docs';
|
|
263
|
+
const command = (0, verification_js_1.formatVerificationCommand)(area, worktree);
|
|
264
|
+
if (command === NO_GATE_NOTICE_ALIAS) {
|
|
265
|
+
log(fmt.status('INFO', `No verification gate configured for area ${area}; skipping pre-review gate check.`));
|
|
266
|
+
return { ok: true, area, command, exitCode: 0, stdout: '', stderr: '' };
|
|
267
|
+
}
|
|
268
|
+
log(fmt.status('INFO', `Pre-review gate for area "${area}": ${fmt.command(command)}`));
|
|
269
|
+
// Run with pipe mode to capture output for auto-bounce fix prompts.
|
|
270
|
+
const result = runFnOverride('bash', ['-lc', command], {
|
|
271
|
+
cwd: worktree,
|
|
272
|
+
stdio: 'pipe',
|
|
273
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
|
274
|
+
});
|
|
275
|
+
const stdout = typeof result.stdout === 'string' ? result.stdout : '';
|
|
276
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr : '';
|
|
277
|
+
if (result.status !== 0) {
|
|
278
|
+
error(fmt.status('FAIL', `Pre-review gate failed for area "${area}" (exit ${result.status}).`));
|
|
279
|
+
if (stderr) {
|
|
280
|
+
error(` stderr: ${stderr.split('\n').slice(0, 10).join('\n ')}`);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
ok: false,
|
|
284
|
+
area,
|
|
285
|
+
command,
|
|
286
|
+
exitCode: result.status,
|
|
287
|
+
stdout,
|
|
288
|
+
stderr,
|
|
289
|
+
error: `verification gate failed with exit code ${result.status}`,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
log(fmt.status('PASS', `Pre-review gate passed for area "${area}".`));
|
|
293
|
+
return { ok: true, area, command, exitCode: 0, stdout, stderr };
|
|
294
|
+
}
|
|
295
|
+
const NO_GATE_NOTICE_ALIAS = ': # no verification gate configured (set adapters.verification.command)';
|
|
296
|
+
/**
|
|
297
|
+
* Handle a pre-review gate failure by auto-bouncing to the implementer.
|
|
298
|
+
* Does NOT consume a reviewer cycle or transition the task out of review status.
|
|
299
|
+
* Tracks retry count in review state metadata.
|
|
300
|
+
* Returns true if bounced, false if retry limit exceeded (mission strands).
|
|
301
|
+
*/
|
|
302
|
+
async function handleGateFailureAutoBounce(slug, worktree, gateResult, implementer, opts = {}) {
|
|
303
|
+
const { startAgentFn = agents_js_1.startAgent, writeReviewStateFn = review_state_js_1.writeReviewState, readReviewStateFn = review_state_js_1.readReviewState, transitionTaskFn = backlog_js_1.transitionTask, applyAgentFallbackFn = applyAgentFallback, taskResolution, enforceTaskAssigneeFn, log = fmt.log.plain, error = fmt.log.plainError, sleepFn: _sleepFn = review_polling_js_1.delay, buildCompactActOnReviewPromptFn: _buildCompactActOnReviewPromptFn = review_prompts_js_1.buildCompactActOnReviewPrompt, isForgejoReviewEnabledFn: _isForgejoReviewEnabledFn, isReviewProviderEnabledFn: _isReviewProviderEnabledFn, legacyIsForgejoReviewEnabledFn: _legacyIsForgejoReviewEnabledFn, exit: _exit = process.exit, } = opts;
|
|
304
|
+
const MAX_GATE_RETRY = 2;
|
|
305
|
+
// Read persisted state to get current retry count
|
|
306
|
+
const persisted = readReviewStateFn(slug, worktree);
|
|
307
|
+
const retryCount = persisted && persisted.metadata && typeof persisted.metadata === 'object'
|
|
308
|
+
? (Number(persisted.metadata.gateFailureRetryCount) || 0)
|
|
309
|
+
: 0;
|
|
310
|
+
if (retryCount >= MAX_GATE_RETRY) {
|
|
311
|
+
error(fmt.status('FAIL', `Pre-review gate failure: max retries exceeded (${MAX_GATE_RETRY}). Mission stranded for ${slug}.`));
|
|
312
|
+
error(fmt.status('FAIL', `Area "${gateResult.area}" verification failed ${retryCount} times. Human intervention required.`));
|
|
313
|
+
error(fmt.status('FAIL', `Gate output:\n${gateResult.stdout || gateResult.stderr || '(no output)'}\n`));
|
|
314
|
+
return { bounced: false, stranded: true };
|
|
315
|
+
}
|
|
316
|
+
// Classify the failure
|
|
317
|
+
const combinedOutput = gateResult.stderr || gateResult.stdout || 'Gate failed with exit code ' + gateResult.exitCode;
|
|
318
|
+
const classification = classifyGateFailure(combinedOutput);
|
|
319
|
+
log(fmt.status('WARN', `Pre-review gate failed for area "${gateResult.area}" (exit ${gateResult.exitCode}). Classification: ${classification.classification}.`));
|
|
320
|
+
// Build fix prompt with captured gate output
|
|
321
|
+
const fixPrompt = [
|
|
322
|
+
`PRE-REVIEW GATE FAILURE — FIX REQUIRED`,
|
|
323
|
+
``,
|
|
324
|
+
`Mission: ${slug}`,
|
|
325
|
+
`Area: ${gateResult.area}`,
|
|
326
|
+
`Gate command: ${gateResult.command}`,
|
|
327
|
+
`Exit code: ${gateResult.exitCode}`,
|
|
328
|
+
``,
|
|
329
|
+
`Gate output (use this to diagnose and fix):`,
|
|
330
|
+
`---`,
|
|
331
|
+
gateResult.stdout || '(no stdout)',
|
|
332
|
+
`---`,
|
|
333
|
+
gateResult.stderr || '(no stderr)',
|
|
334
|
+
`---`,
|
|
335
|
+
``,
|
|
336
|
+
`Classification: ${classification.classification} — ${classification.action}`,
|
|
337
|
+
`Retry attempt: ${retryCount + 1}/${MAX_GATE_RETRY}`,
|
|
338
|
+
``,
|
|
339
|
+
`Fix the underlying issue so the verification gate passes for area "${gateResult.area}".`,
|
|
340
|
+
`After fixing, the review loop will re-run the gate before the next review round.`,
|
|
341
|
+
].join('\n');
|
|
342
|
+
// Increment retry count in metadata
|
|
343
|
+
if (!persisted || !persisted.metadata || typeof persisted.metadata !== 'object') {
|
|
344
|
+
// Create new metadata
|
|
345
|
+
}
|
|
346
|
+
const metadata = persisted && persisted.metadata && typeof persisted.metadata === 'object'
|
|
347
|
+
? { ...persisted.metadata }
|
|
348
|
+
: {};
|
|
349
|
+
metadata.gateFailureRetryCount = retryCount + 1;
|
|
350
|
+
// Update review state with incremented retry count
|
|
351
|
+
if (persisted) {
|
|
352
|
+
const updatedState = { ...persisted, metadata };
|
|
353
|
+
writeReviewStateFn(slug, updatedState, worktree);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
writeReviewStateFn(slug, { metadata }, worktree);
|
|
357
|
+
}
|
|
358
|
+
// Transition task back to active (implementer phase) without consuming reviewer cycle
|
|
359
|
+
transitionTaskFn(slug, 'active', { rootDir: worktree, log });
|
|
360
|
+
log(fmt.status('INFO', `Auto-bouncing to implementer (${implementer}) with fix prompt. Retry ${retryCount + 1}/${MAX_GATE_RETRY}.`));
|
|
361
|
+
// Launch implementer with the fix prompt
|
|
362
|
+
try {
|
|
363
|
+
const launchResult = await startAgentFn('act-on-review', {
|
|
364
|
+
agent: implementer,
|
|
365
|
+
prompt: (_actualImplementer) => fixPrompt,
|
|
366
|
+
worktree,
|
|
367
|
+
slug,
|
|
368
|
+
role: 'implementer',
|
|
369
|
+
exclude: [],
|
|
370
|
+
});
|
|
371
|
+
// Apply any agent fallback if needed
|
|
372
|
+
implementer = applyAgentFallbackFn({
|
|
373
|
+
role: 'implementer',
|
|
374
|
+
original: implementer,
|
|
375
|
+
launchResult,
|
|
376
|
+
state: persisted || {},
|
|
377
|
+
slug,
|
|
378
|
+
worktree,
|
|
379
|
+
taskResolution,
|
|
380
|
+
log,
|
|
381
|
+
writeReviewStateFn,
|
|
382
|
+
enforceTaskAssigneeFn,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
error(fmt.status('FAIL', `Could not relaunch implementer (${implementer}) for gate failure auto-bounce: ${err.message}`));
|
|
387
|
+
return { bounced: false, stranded: true };
|
|
388
|
+
}
|
|
389
|
+
log(fmt.status('INFO', `Implementer (${implementer}) relaunched with gate failure fix prompt.`));
|
|
390
|
+
return { bounced: true, stranded: false };
|
|
391
|
+
}
|
|
392
|
+
// ============================================================================
|
|
235
393
|
// Main Review Loop
|
|
236
394
|
// ============================================================================
|
|
237
395
|
async function startReviewLoop(slug, opts = {}) {
|
|
238
|
-
let { implementer, reviewer, focus = 'all', maxAttempts = DEFAULT_MAX_ATTEMPTS, dryRun = false, reset = false, continue: continueFlag = false, isContinue = false, verbose = false, pollTimeoutSeconds = null, worktree: callerWorktree, missionPath, resetReviewStateFn = review_state_js_1.resetReviewState, maybeUpdateGraphifyBeforeReviewFn = maybeUpdateGraphifyBeforeReview, readReviewStateFn = review_state_js_1.readReviewState, resolveTaskFileFn = backlog_js_1.resolveTaskFile, getTaskImplementerFn = backlog_js_1.getTaskImplementer, getTaskStatusFn = backlog_js_1.getTaskStatus, transitionTaskFn = backlog_js_1.transitionTask, toVirtualFn = state_map_js_1.toVirtual, transitionVirtualFn = state_map_js_1.transitionVirtual, workflowLauncherStatusFn = agents_js_1.workflowLauncherStatus, buildAutonomousReviewMatrixFn = runtime_matrix_js_1.buildAutonomousReviewMatrix, formatMatrixSummaryFn = runtime_matrix_js_1.formatMatrixSummary, selectAgentFn = agents_js_1.selectAgent, providerAvailableFn = undefined, runFn = git_js_1.run, getPrStatusFn = review_adapter_js_1.getPrStatus, enforceTaskAssigneeFn = backlog_js_1.enforceTaskAssignee, resolveReviewUserFn = undefined, forgejoAvailableFn = null, resolveForgejoUserFn = null, readTokenFn = review_adapter_js_1.readToken, getCommentsFn = review_adapter_js_1.getComments, postCommentFn = review_adapter_js_1.postComment, postReviewFn = review_adapter_js_1.postReview, writeReviewStateFn = review_state_js_1.writeReviewState, startAgentFn = agents_js_1.startAgent, pollForReviewFn = review_polling_js_1.pollForReview, pollForDispositionFn = review_polling_js_1.pollForDisposition, applyAgentFallbackFn = applyAgentFallback, buildReviewPromptFn = review_prompts_js_1.buildReviewPrompt, buildActOnReviewPromptFn = review_prompts_js_1.buildActOnReviewPrompt, buildCompactReviewPromptFn = review_prompts_js_1.buildCompactReviewPrompt, buildCompactActOnReviewPromptFn = review_prompts_js_1.buildCompactActOnReviewPrompt, consumeReviewerArtifactsFn = review_artifacts_js_1.consumeReviewerArtifacts, consumeImplementerArtifactsFn = review_artifacts_js_1.consumeImplementerArtifacts, rebaseBeforeReviewRoundFn = rebase_js_1.rebaseBeforeReviewRound, eligibleAgentsForStepFn = agents_js_1.eligibleAgentsForStep, log = fmt.log.plain, error = fmt.log.plainError, getLatestReviewForPrFn = review_adapter_js_1.getLatestReviewForPr, getLatestDispositionForPrFn = review_adapter_js_1.getLatestDispositionForPr, sleepFn = review_polling_js_1.delay, exit = process.exit, gitFn = git_js_1.git, isReviewProviderEnabledFn = undefined, legacyIsForgejoReviewEnabledFn = null, isForgejoReviewEnabledFn = null, recordStageStatsSafeFn = () => { } } = opts;
|
|
396
|
+
let { implementer, reviewer, focus = 'all', maxAttempts = DEFAULT_MAX_ATTEMPTS, dryRun = false, reset = false, continue: continueFlag = false, isContinue = false, verbose = false, pollTimeoutSeconds = null, worktree: callerWorktree, missionPath, runPreReviewGateFn = runPreReviewGate, handleGateFailureAutoBounceFn = handleGateFailureAutoBounce, resetReviewStateFn = review_state_js_1.resetReviewState, maybeUpdateGraphifyBeforeReviewFn = maybeUpdateGraphifyBeforeReview, readReviewStateFn = review_state_js_1.readReviewState, resolveTaskFileFn = backlog_js_1.resolveTaskFile, getTaskImplementerFn = backlog_js_1.getTaskImplementer, getTaskStatusFn = backlog_js_1.getTaskStatus, transitionTaskFn = backlog_js_1.transitionTask, toVirtualFn = state_map_js_1.toVirtual, transitionVirtualFn = state_map_js_1.transitionVirtual, workflowLauncherStatusFn = agents_js_1.workflowLauncherStatus, buildAutonomousReviewMatrixFn = runtime_matrix_js_1.buildAutonomousReviewMatrix, formatMatrixSummaryFn = runtime_matrix_js_1.formatMatrixSummary, selectAgentFn = agents_js_1.selectAgent, providerAvailableFn = undefined, runFn = git_js_1.run, getPrStatusFn = review_adapter_js_1.getPrStatus, enforceTaskAssigneeFn = backlog_js_1.enforceTaskAssignee, resolveReviewUserFn = undefined, forgejoAvailableFn = null, resolveForgejoUserFn = null, readTokenFn = review_adapter_js_1.readToken, getCommentsFn = review_adapter_js_1.getComments, postCommentFn = review_adapter_js_1.postComment, postReviewFn = review_adapter_js_1.postReview, writeReviewStateFn = review_state_js_1.writeReviewState, startAgentFn = agents_js_1.startAgent, pollForReviewFn = review_polling_js_1.pollForReview, pollForDispositionFn = review_polling_js_1.pollForDisposition, applyAgentFallbackFn = applyAgentFallback, buildReviewPromptFn = review_prompts_js_1.buildReviewPrompt, buildActOnReviewPromptFn = review_prompts_js_1.buildActOnReviewPrompt, buildCompactReviewPromptFn = review_prompts_js_1.buildCompactReviewPrompt, buildCompactActOnReviewPromptFn = review_prompts_js_1.buildCompactActOnReviewPrompt, consumeReviewerArtifactsFn = review_artifacts_js_1.consumeReviewerArtifacts, consumeImplementerArtifactsFn = review_artifacts_js_1.consumeImplementerArtifacts, rebaseBeforeReviewRoundFn = rebase_js_1.rebaseBeforeReviewRound, eligibleAgentsForStepFn = agents_js_1.eligibleAgentsForStep, log = fmt.log.plain, error = fmt.log.plainError, getLatestReviewForPrFn = review_adapter_js_1.getLatestReviewForPr, getLatestDispositionForPrFn = review_adapter_js_1.getLatestDispositionForPr, sleepFn = review_polling_js_1.delay, exit = process.exit, gitFn = git_js_1.git, isReviewProviderEnabledFn = undefined, legacyIsForgejoReviewEnabledFn = null, isForgejoReviewEnabledFn = null, recordStageStatsSafeFn = () => { } } = opts;
|
|
239
397
|
const performHandoffFn = opts.performHandoffFn || (await getHandoff()).performHandoff;
|
|
240
398
|
let prNumber = null;
|
|
241
399
|
isContinue = Boolean(isContinue || continueFlag);
|
|
@@ -595,6 +753,30 @@ async function startReviewLoop(slug, opts = {}) {
|
|
|
595
753
|
if (attempt > state.round) {
|
|
596
754
|
state.advanceRound();
|
|
597
755
|
}
|
|
756
|
+
// Snapshot the primary branch's HEAD commit for this round. This is a
|
|
757
|
+
// fallback value only, used for paths that don't rebase this round (a
|
|
758
|
+
// dry run, or resuming with an existing reviewState): once
|
|
759
|
+
// rebaseBeforeReviewRoundFn runs below, HEAD is rebased onto primary's
|
|
760
|
+
// *current* tip, so the baseline is re-captured immediately after the
|
|
761
|
+
// rebase completes (see below). Capturing it here, before the rebase,
|
|
762
|
+
// would pin the diff to a stale pre-rebase SHA; since rebase replays
|
|
763
|
+
// primary's newer commits into HEAD's ancestry, diffing against that
|
|
764
|
+
// stale SHA would surface exactly the "not rebased to main" noise this
|
|
765
|
+
// baseline exists to suppress (task-1407).
|
|
766
|
+
// Falls back to undefined (letting the prompt builders resolve the live
|
|
767
|
+
// primary branch ref themselves) if the primary branch cannot be detected,
|
|
768
|
+
// e.g. in a repo with no main/master branch yet.
|
|
769
|
+
const captureReviewBaseline = () => {
|
|
770
|
+
try {
|
|
771
|
+
const primaryBranchName = (0, mission_utils_js_1.getPrimaryBranch)(worktree, gitFn);
|
|
772
|
+
const reviewBaselineResult = gitFn(['-C', worktree, 'rev-parse', primaryBranchName]);
|
|
773
|
+
return (reviewBaselineResult.stdout || '').trim() || primaryBranchName;
|
|
774
|
+
}
|
|
775
|
+
catch {
|
|
776
|
+
return undefined;
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
let reviewBaseline = captureReviewBaseline();
|
|
598
780
|
let reviewState;
|
|
599
781
|
if (state.phase === 'reviewing') {
|
|
600
782
|
// Check if we can skip reviewer launch
|
|
@@ -626,7 +808,7 @@ async function startReviewLoop(slug, opts = {}) {
|
|
|
626
808
|
}
|
|
627
809
|
else {
|
|
628
810
|
log(`\n--- DRY-RUN: reviewer (${reviewer}) prompt ---`);
|
|
629
|
-
log(buildReviewPromptFn({ reviewer: reviewer, branch, implementer: implementer, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer: '{{AGENT_NAME}}' }));
|
|
811
|
+
log(buildReviewPromptFn({ reviewer: reviewer, branch, implementer: implementer, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer: '{{AGENT_NAME}}', reviewBaseline }));
|
|
630
812
|
}
|
|
631
813
|
}
|
|
632
814
|
}
|
|
@@ -643,11 +825,58 @@ async function startReviewLoop(slug, opts = {}) {
|
|
|
643
825
|
exit(1);
|
|
644
826
|
return;
|
|
645
827
|
}
|
|
828
|
+
// Re-capture after rebase: HEAD is now rebased onto primary's tip,
|
|
829
|
+
// so the pre-rebase snapshot above is stale and must be replaced
|
|
830
|
+
// with the SHA that HEAD was actually rebased onto (task-1407).
|
|
831
|
+
reviewBaseline = captureReviewBaseline();
|
|
646
832
|
if (!dryRun) {
|
|
647
833
|
transitionTaskFn(slug, 'review', { rootDir: worktree, log });
|
|
648
834
|
}
|
|
649
835
|
state.phase = 'reviewing';
|
|
650
836
|
writeReviewStateFn(slug, state, worktree);
|
|
837
|
+
// Pre-review gate enforcement (ADR 0048 Control C1 / TASK-1385):
|
|
838
|
+
// Run the verification gate before the reviewer launches. On failure,
|
|
839
|
+
// auto-bounce to the implementer with captured gate output as fix prompt.
|
|
840
|
+
// No reviewer cycle is consumed when the gate fails and auto-bounce occurs.
|
|
841
|
+
if (!dryRun) {
|
|
842
|
+
const preReviewGateResult = await runPreReviewGateFn(slug, worktree, {
|
|
843
|
+
runFn: runFn,
|
|
844
|
+
log,
|
|
845
|
+
error,
|
|
846
|
+
});
|
|
847
|
+
if (!preReviewGateResult.ok) {
|
|
848
|
+
log(fmt.status('WARN', `Pre-review gate failed for area "${preReviewGateResult.area}" (exit ${preReviewGateResult.exitCode}). Auto-bouncing to implementer.`));
|
|
849
|
+
const bounceResult = await handleGateFailureAutoBounceFn(slug, worktree, preReviewGateResult, implementer, {
|
|
850
|
+
startAgentFn: startAgentFn,
|
|
851
|
+
writeReviewStateFn: writeReviewStateFn,
|
|
852
|
+
readReviewStateFn: readReviewStateFn,
|
|
853
|
+
transitionTaskFn: transitionTaskFn,
|
|
854
|
+
applyAgentFallbackFn: applyAgentFallbackFn,
|
|
855
|
+
taskResolution,
|
|
856
|
+
enforceTaskAssigneeFn,
|
|
857
|
+
log,
|
|
858
|
+
error,
|
|
859
|
+
sleepFn,
|
|
860
|
+
buildCompactActOnReviewPromptFn,
|
|
861
|
+
isForgejoReviewEnabledFn,
|
|
862
|
+
isReviewProviderEnabledFn,
|
|
863
|
+
legacyIsForgejoReviewEnabledFn,
|
|
864
|
+
exit,
|
|
865
|
+
});
|
|
866
|
+
if (bounceResult.stranded) {
|
|
867
|
+
error(fmt.status('FAIL', `Pre-review gate failure stranded mission ${slug}. Exiting review loop.`));
|
|
868
|
+
exit(1);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (bounceResult.bounced) {
|
|
872
|
+
log(fmt.status('INFO', `Auto-bounce succeeded. Continuing to next round.`));
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
log(fmt.status('PASS', `Pre-review gate passed for area "${preReviewGateResult.area}".`));
|
|
878
|
+
}
|
|
879
|
+
}
|
|
651
880
|
if (reviewer === 'autonomous' && !forgejoEnabled) {
|
|
652
881
|
log(fmt.status('INFO', `Round ${attempt}: reviewer identity is autonomous; skipping reviewer launch and using local review artifacts only.`));
|
|
653
882
|
}
|
|
@@ -657,7 +886,7 @@ async function startReviewLoop(slug, opts = {}) {
|
|
|
657
886
|
try {
|
|
658
887
|
reviewerLaunchResult = await startAgentFn('review', {
|
|
659
888
|
agent: reviewer,
|
|
660
|
-
prompt: (actualReviewer) => buildCompactReviewPromptFn({ reviewer: reviewer, branch, implementer: implementer, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer }),
|
|
889
|
+
prompt: (actualReviewer) => buildCompactReviewPromptFn({ reviewer: reviewer, branch, implementer: implementer, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer, reviewBaseline }),
|
|
661
890
|
worktree, slug, role: 'reviewer', exclude: [implementer]
|
|
662
891
|
});
|
|
663
892
|
}
|
|
@@ -722,7 +951,7 @@ async function startReviewLoop(slug, opts = {}) {
|
|
|
722
951
|
try {
|
|
723
952
|
relaunchResult = await startAgentFn('review', {
|
|
724
953
|
agent: reviewer,
|
|
725
|
-
prompt: (actualReviewer) => buildCompactReviewPromptFn({ reviewer: reviewer, branch, implementer: implementer, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer }) + '\n\n' + recoveryPrompt,
|
|
954
|
+
prompt: (actualReviewer) => buildCompactReviewPromptFn({ reviewer: reviewer, branch, implementer: implementer, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer, reviewBaseline }) + '\n\n' + recoveryPrompt,
|
|
726
955
|
worktree, slug, role: 'reviewer', exclude: [implementer]
|
|
727
956
|
});
|
|
728
957
|
}
|
|
@@ -864,7 +1093,7 @@ async function startReviewLoop(slug, opts = {}) {
|
|
|
864
1093
|
// First launch or re-launch after stale BLOCKED/PARKED
|
|
865
1094
|
if (dryRun) {
|
|
866
1095
|
log(`\n--- DRY-RUN: implementer (${implementer}) act-on-review prompt ---`);
|
|
867
|
-
log(buildActOnReviewPromptFn({ implementer: implementer, branch, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer: '{{AGENT_NAME}}' }));
|
|
1096
|
+
log(buildActOnReviewPromptFn({ implementer: implementer, branch, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer: '{{AGENT_NAME}}', reviewBaseline }));
|
|
868
1097
|
if (reLaunch) {
|
|
869
1098
|
log(fmt.status('INFO', `Round ${attempt}: stale BLOCKED/PARKED disposition replaced by fresh implementer action.`));
|
|
870
1099
|
}
|
|
@@ -881,7 +1110,7 @@ async function startReviewLoop(slug, opts = {}) {
|
|
|
881
1110
|
try {
|
|
882
1111
|
implementerLaunchResult = await startAgentFn('act-on-review', {
|
|
883
1112
|
agent: implementer,
|
|
884
|
-
prompt: (actualImplementer) => buildCompactActOnReviewPromptFn({ implementer: implementer, branch, attempt, reviewOutcome: reviewState, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer }),
|
|
1113
|
+
prompt: (actualImplementer) => buildCompactActOnReviewPromptFn({ implementer: implementer, branch, attempt, reviewOutcome: reviewState, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer, reviewBaseline }),
|
|
885
1114
|
worktree, slug, role: 'implementer', exclude: [reviewer]
|
|
886
1115
|
});
|
|
887
1116
|
}
|
|
@@ -943,7 +1172,7 @@ async function startReviewLoop(slug, opts = {}) {
|
|
|
943
1172
|
try {
|
|
944
1173
|
relaunchResult = await startAgentFn('act-on-review', {
|
|
945
1174
|
agent: implementer,
|
|
946
|
-
prompt: (actualImplementer) => buildCompactActOnReviewPromptFn({ implementer: implementer, branch, attempt, reviewOutcome: reviewState, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer }) + '\n\n' + recoveryPrompt,
|
|
1175
|
+
prompt: (actualImplementer) => buildCompactActOnReviewPromptFn({ implementer: implementer, branch, attempt, reviewOutcome: reviewState, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer, reviewBaseline }) + '\n\n' + recoveryPrompt,
|
|
947
1176
|
worktree, slug, role: 'implementer', exclude: [reviewer]
|
|
948
1177
|
});
|
|
949
1178
|
}
|