@magnusekdahl/parallix 1.3.2 → 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.
Files changed (50) hide show
  1. package/docs/agents.md +1 -1
  2. package/docs/use-cases.md +1 -1
  3. package/lib/agents/agents.js +20 -3
  4. package/lib/agents/agents.ts +14 -3
  5. package/lib/agents/mistral-telemetry.js +122 -23
  6. package/lib/agents/mistral-telemetry.ts +141 -26
  7. package/lib/agents/mistral.js +128 -14
  8. package/lib/agents/mistral.ts +145 -5
  9. package/lib/commands/active.js +69 -39
  10. package/lib/commands/active.ts +97 -60
  11. package/lib/commands/config.ts +3 -3
  12. package/lib/commands/coverage-gate.ts +1 -1
  13. package/lib/commands/draft.js +1 -1
  14. package/lib/commands/draft.ts +1 -1
  15. package/lib/commands/handoff.js +13 -8
  16. package/lib/commands/handoff.ts +24 -18
  17. package/lib/commands/rebase.js +1 -1
  18. package/lib/commands/rebase.ts +2 -2
  19. package/lib/commands/repair-handoff.js +141 -20
  20. package/lib/commands/repair-handoff.ts +185 -45
  21. package/lib/commands/resolve-conflict.js +1 -1
  22. package/lib/commands/resolve-conflict.ts +2 -2
  23. package/lib/commands/stats-backfill.ts +10 -10
  24. package/lib/commands/stats.js +38 -11
  25. package/lib/commands/stats.ts +40 -96
  26. package/lib/core/fmt.ts +2 -2
  27. package/lib/core/git.ts +2 -2
  28. package/lib/core/gitignore.ts +2 -2
  29. package/lib/core/mission-utils.js +2 -2
  30. package/lib/core/mission-utils.ts +2 -2
  31. package/lib/core/persistent-data-migration.ts +2 -2
  32. package/lib/core/spawn-tee.ts +1 -1
  33. package/lib/core/state-map.ts +2 -2
  34. package/lib/core/storage.ts +1 -1
  35. package/lib/core/verification.ts +1 -1
  36. package/lib/review/rebase.ts +12 -12
  37. package/lib/review/review-artifacts.ts +35 -35
  38. package/lib/review/review-commands.ts +40 -40
  39. package/lib/review/review-events.ts +12 -12
  40. package/lib/review/review-loop.js +236 -7
  41. package/lib/review/review-loop.ts +338 -22
  42. package/lib/review/review-polling.ts +6 -6
  43. package/lib/review/review-prompts.js +8 -4
  44. package/lib/review/review-prompts.ts +12 -8
  45. package/lib/review/review-state.js +1 -1
  46. package/lib/review/review-state.ts +2 -2
  47. package/package.json +3 -2
  48. package/prompts/review-verbose.md +1 -1
  49. package/prompts/review.md +1 -1
  50. package/px.js +8 -4
@@ -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
  }