@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.
- 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-telemetry.js +122 -23
- package/lib/agents/mistral-telemetry.ts +141 -26
- 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
- package/px.js +8 -4
|
@@ -8,7 +8,8 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import * as fmt from '../core/fmt.js';
|
|
10
10
|
import { git, run } from '../core/git.js';
|
|
11
|
-
import { findMissionDir, resolveWorktree, missionBranchName } from '../core/mission-utils.js';
|
|
11
|
+
import { findMissionDir, findMissionArea, resolveWorktree, missionBranchName, getPrimaryBranch } from '../core/mission-utils.js';
|
|
12
|
+
import { formatVerificationCommand } from '../core/verification.js';
|
|
12
13
|
import { resolveTaskFile, getTaskImplementer, getTaskStatus, enforceTaskAssignee, transitionTask, reportTaskResolution } from '../tools/backlog.js';
|
|
13
14
|
import { toVirtual, transitionVirtual } from '../core/state-map.js';
|
|
14
15
|
import { getPrStatus, readToken, getLatestReviewForPr, getLatestDispositionForPr, providerAvailable, getComments, postComment, postReview, resolveReviewUser, isProviderEnabled } from './review-adapter.js';
|
|
@@ -98,8 +99,8 @@ export function recordStageStatsSafe(
|
|
|
98
99
|
reviewer?: string;
|
|
99
100
|
result?: Record<string, any>;
|
|
100
101
|
sinceMs?: number;
|
|
101
|
-
log?: (
|
|
102
|
-
error?: (
|
|
102
|
+
log?: (_msg: string) => void;
|
|
103
|
+
error?: (_msg: string) => void;
|
|
103
104
|
state?: Record<string, any>;
|
|
104
105
|
writeReviewStateFn?: typeof writeReviewState;
|
|
105
106
|
model?: string | null;
|
|
@@ -149,7 +150,7 @@ const _require = createRequire(__filename);
|
|
|
149
150
|
|
|
150
151
|
export function maybeUpdateGraphifyBeforeReview(
|
|
151
152
|
rootDir: string,
|
|
152
|
-
{ commandRunner = run, log = fmt.log.plain }: { commandRunner?: typeof run; log?: (
|
|
153
|
+
{ commandRunner = run, log = fmt.log.plain }: { commandRunner?: typeof run; log?: (_msg: string) => void } = {}
|
|
153
154
|
): unknown {
|
|
154
155
|
// Lazy require to break circular dependency with core/mission-utils
|
|
155
156
|
const { updateGraphifyKnowledgeGraph } = _require('../core/mission-utils.js');
|
|
@@ -180,7 +181,7 @@ export function applyAgentFallback(opts: {
|
|
|
180
181
|
slug: string;
|
|
181
182
|
worktree?: string;
|
|
182
183
|
taskResolution?: Record<string, any>;
|
|
183
|
-
log?: (
|
|
184
|
+
log?: (_msg: string) => void;
|
|
184
185
|
writeReviewStateFn?: typeof writeReviewState;
|
|
185
186
|
enforceTaskAssigneeFn?: typeof enforceTaskAssignee;
|
|
186
187
|
}): string {
|
|
@@ -208,7 +209,7 @@ export function persistNormalizedPhaseRepair(
|
|
|
208
209
|
slug: string,
|
|
209
210
|
state: ReviewState,
|
|
210
211
|
worktree: string,
|
|
211
|
-
{ log = fmt.log.plain, writeReviewStateFn = writeReviewState }: { log?: (
|
|
212
|
+
{ log = fmt.log.plain, writeReviewStateFn = writeReviewState }: { log?: (_msg: string) => void; writeReviewStateFn?: typeof writeReviewState } = {}
|
|
212
213
|
): void {
|
|
213
214
|
if (!state || !state.phaseOriginal || (VALID_PHASES as readonly string[]).includes(state.phaseOriginal)) {
|
|
214
215
|
return;
|
|
@@ -237,6 +238,245 @@ export function stageLaunchSinceMs(result: { startedAt?: string } | null | undef
|
|
|
237
238
|
return Number.isFinite(startedMs) ? startedMs : 0;
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// Pre-review Gate Enforcement (ADR 0048 Control C1 / TASK-1385)
|
|
243
|
+
// ============================================================================
|
|
244
|
+
|
|
245
|
+
// Minimal error classifier stub for gate failures (TASK-1389 dependency).
|
|
246
|
+
// Maps gate failures to Class 6: genuine gate failure — code issue, dispatch: auto-send-back.
|
|
247
|
+
// TASK-1389 will replace this with the full 8-class dispatch table.
|
|
248
|
+
const GATE_FAILURE_CLASS = 'class-6-genuine-gate-failure';
|
|
249
|
+
const GATE_FAILURE_ACTION = 'auto-send-back';
|
|
250
|
+
|
|
251
|
+
export function classifyGateFailure(_output: string): { classification: string; action: string; isRelaunchable: boolean } {
|
|
252
|
+
// Gate failures are genuine code issues — always relaunchable via auto-send-back.
|
|
253
|
+
// The full classifier (TASK-1389) will expand this to 8 classes with nuanced dispatch.
|
|
254
|
+
return {
|
|
255
|
+
classification: GATE_FAILURE_CLASS,
|
|
256
|
+
action: GATE_FAILURE_ACTION,
|
|
257
|
+
isRelaunchable: true,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface PreReviewGateResult {
|
|
262
|
+
ok: boolean;
|
|
263
|
+
area: string;
|
|
264
|
+
command: string;
|
|
265
|
+
exitCode: number | null;
|
|
266
|
+
stdout: string;
|
|
267
|
+
stderr: string;
|
|
268
|
+
error?: string;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Run the verification gate with the mission area before a review round.
|
|
273
|
+
* Captures stdout/stderr for use in auto-bounce fix prompts.
|
|
274
|
+
*/
|
|
275
|
+
export async function runPreReviewGate(
|
|
276
|
+
slug: string,
|
|
277
|
+
worktree: string,
|
|
278
|
+
opts: {
|
|
279
|
+
findMissionAreaFn?: typeof findMissionArea;
|
|
280
|
+
runFn?: typeof run;
|
|
281
|
+
log?: (_msg: string) => void;
|
|
282
|
+
error?: (_msg: string) => void;
|
|
283
|
+
} = {}
|
|
284
|
+
): Promise<PreReviewGateResult> {
|
|
285
|
+
const {
|
|
286
|
+
findMissionAreaFn = findMissionArea,
|
|
287
|
+
runFn: runFnOverride = run,
|
|
288
|
+
log = fmt.log.plain,
|
|
289
|
+
error = fmt.log.plainError,
|
|
290
|
+
} = opts;
|
|
291
|
+
|
|
292
|
+
const missionDir = findMissionDir(slug, worktree);
|
|
293
|
+
const area = missionDir ? findMissionAreaFn(missionDir) : 'docs';
|
|
294
|
+
const command = formatVerificationCommand(area, worktree);
|
|
295
|
+
|
|
296
|
+
if (command === NO_GATE_NOTICE_ALIAS) {
|
|
297
|
+
log(fmt.status('INFO', `No verification gate configured for area ${area}; skipping pre-review gate check.`));
|
|
298
|
+
return { ok: true, area, command, exitCode: 0, stdout: '', stderr: '' };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
log(fmt.status('INFO', `Pre-review gate for area "${area}": ${fmt.command(command)}`));
|
|
302
|
+
|
|
303
|
+
// Run with pipe mode to capture output for auto-bounce fix prompts.
|
|
304
|
+
const result = runFnOverride('bash', ['-lc', command], {
|
|
305
|
+
cwd: worktree,
|
|
306
|
+
stdio: 'pipe',
|
|
307
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const stdout = typeof result.stdout === 'string' ? result.stdout : '';
|
|
311
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr : '';
|
|
312
|
+
|
|
313
|
+
if (result.status !== 0) {
|
|
314
|
+
error(fmt.status('FAIL', `Pre-review gate failed for area "${area}" (exit ${result.status}).`));
|
|
315
|
+
if (stderr) {
|
|
316
|
+
error(` stderr: ${stderr.split('\n').slice(0, 10).join('\n ')}`);
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
ok: false,
|
|
320
|
+
area,
|
|
321
|
+
command,
|
|
322
|
+
exitCode: result.status,
|
|
323
|
+
stdout,
|
|
324
|
+
stderr,
|
|
325
|
+
error: `verification gate failed with exit code ${result.status}`,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
log(fmt.status('PASS', `Pre-review gate passed for area "${area}".`));
|
|
330
|
+
return { ok: true, area, command, exitCode: 0, stdout, stderr };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const NO_GATE_NOTICE_ALIAS = ': # no verification gate configured (set adapters.verification.command)';
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Handle a pre-review gate failure by auto-bouncing to the implementer.
|
|
337
|
+
* Does NOT consume a reviewer cycle or transition the task out of review status.
|
|
338
|
+
* Tracks retry count in review state metadata.
|
|
339
|
+
* Returns true if bounced, false if retry limit exceeded (mission strands).
|
|
340
|
+
*/
|
|
341
|
+
export async function handleGateFailureAutoBounce(
|
|
342
|
+
slug: string,
|
|
343
|
+
worktree: string,
|
|
344
|
+
gateResult: PreReviewGateResult,
|
|
345
|
+
implementer: string,
|
|
346
|
+
opts: {
|
|
347
|
+
startAgentFn?: typeof startAgent;
|
|
348
|
+
writeReviewStateFn?: typeof writeReviewState;
|
|
349
|
+
readReviewStateFn?: typeof readReviewState;
|
|
350
|
+
transitionTaskFn?: typeof transitionTask;
|
|
351
|
+
applyAgentFallbackFn?: typeof applyAgentFallback;
|
|
352
|
+
taskResolution?: { ok: boolean; taskFile?: string };
|
|
353
|
+
enforceTaskAssigneeFn?: typeof enforceTaskAssignee;
|
|
354
|
+
log?: (_msg: string) => void;
|
|
355
|
+
error?: (_msg: string) => void;
|
|
356
|
+
sleepFn?: typeof delay;
|
|
357
|
+
buildCompactActOnReviewPromptFn?: typeof buildCompactActOnReviewPrompt;
|
|
358
|
+
isForgejoReviewEnabledFn?: ((_rootDir?: string) => boolean) | null;
|
|
359
|
+
isReviewProviderEnabledFn?: ((_rootDir?: string) => boolean) | null;
|
|
360
|
+
legacyIsForgejoReviewEnabledFn?: ((_rootDir?: string) => boolean) | null;
|
|
361
|
+
exit?: (_code: number) => never;
|
|
362
|
+
} = {}
|
|
363
|
+
): Promise<{ bounced: boolean; stranded: boolean }> {
|
|
364
|
+
const {
|
|
365
|
+
startAgentFn = startAgent,
|
|
366
|
+
writeReviewStateFn = writeReviewState,
|
|
367
|
+
readReviewStateFn = readReviewState,
|
|
368
|
+
transitionTaskFn = transitionTask,
|
|
369
|
+
applyAgentFallbackFn = applyAgentFallback,
|
|
370
|
+
taskResolution,
|
|
371
|
+
enforceTaskAssigneeFn,
|
|
372
|
+
log = fmt.log.plain,
|
|
373
|
+
error = fmt.log.plainError,
|
|
374
|
+
sleepFn: _sleepFn = delay,
|
|
375
|
+
buildCompactActOnReviewPromptFn: _buildCompactActOnReviewPromptFn = buildCompactActOnReviewPrompt,
|
|
376
|
+
isForgejoReviewEnabledFn: _isForgejoReviewEnabledFn,
|
|
377
|
+
isReviewProviderEnabledFn: _isReviewProviderEnabledFn,
|
|
378
|
+
legacyIsForgejoReviewEnabledFn: _legacyIsForgejoReviewEnabledFn,
|
|
379
|
+
exit: _exit = process.exit,
|
|
380
|
+
} = opts;
|
|
381
|
+
|
|
382
|
+
const MAX_GATE_RETRY = 2;
|
|
383
|
+
|
|
384
|
+
// Read persisted state to get current retry count
|
|
385
|
+
const persisted = readReviewStateFn(slug, worktree);
|
|
386
|
+
const retryCount = persisted && persisted.metadata && typeof persisted.metadata === 'object'
|
|
387
|
+
? (Number((persisted.metadata as any).gateFailureRetryCount) || 0)
|
|
388
|
+
: 0;
|
|
389
|
+
|
|
390
|
+
if (retryCount >= MAX_GATE_RETRY) {
|
|
391
|
+
error(fmt.status('FAIL', `Pre-review gate failure: max retries exceeded (${MAX_GATE_RETRY}). Mission stranded for ${slug}.`));
|
|
392
|
+
error(fmt.status('FAIL', `Area "${gateResult.area}" verification failed ${retryCount} times. Human intervention required.`));
|
|
393
|
+
error(fmt.status('FAIL', `Gate output:\n${gateResult.stdout || gateResult.stderr || '(no output)'}\n`));
|
|
394
|
+
return { bounced: false, stranded: true };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Classify the failure
|
|
398
|
+
const combinedOutput = gateResult.stderr || gateResult.stdout || 'Gate failed with exit code ' + gateResult.exitCode;
|
|
399
|
+
const classification = classifyGateFailure(combinedOutput);
|
|
400
|
+
|
|
401
|
+
log(fmt.status('WARN', `Pre-review gate failed for area "${gateResult.area}" (exit ${gateResult.exitCode}). Classification: ${classification.classification}.`));
|
|
402
|
+
|
|
403
|
+
// Build fix prompt with captured gate output
|
|
404
|
+
const fixPrompt = [
|
|
405
|
+
`PRE-REVIEW GATE FAILURE — FIX REQUIRED`,
|
|
406
|
+
``,
|
|
407
|
+
`Mission: ${slug}`,
|
|
408
|
+
`Area: ${gateResult.area}`,
|
|
409
|
+
`Gate command: ${gateResult.command}`,
|
|
410
|
+
`Exit code: ${gateResult.exitCode}`,
|
|
411
|
+
``,
|
|
412
|
+
`Gate output (use this to diagnose and fix):`,
|
|
413
|
+
`---`,
|
|
414
|
+
gateResult.stdout || '(no stdout)',
|
|
415
|
+
`---`,
|
|
416
|
+
gateResult.stderr || '(no stderr)',
|
|
417
|
+
`---`,
|
|
418
|
+
``,
|
|
419
|
+
`Classification: ${classification.classification} — ${classification.action}`,
|
|
420
|
+
`Retry attempt: ${retryCount + 1}/${MAX_GATE_RETRY}`,
|
|
421
|
+
``,
|
|
422
|
+
`Fix the underlying issue so the verification gate passes for area "${gateResult.area}".`,
|
|
423
|
+
`After fixing, the review loop will re-run the gate before the next review round.`,
|
|
424
|
+
].join('\n');
|
|
425
|
+
|
|
426
|
+
// Increment retry count in metadata
|
|
427
|
+
if (!persisted || !persisted.metadata || typeof persisted.metadata !== 'object') {
|
|
428
|
+
// Create new metadata
|
|
429
|
+
}
|
|
430
|
+
const metadata = persisted && persisted.metadata && typeof persisted.metadata === 'object'
|
|
431
|
+
? { ...persisted.metadata }
|
|
432
|
+
: {};
|
|
433
|
+
metadata.gateFailureRetryCount = retryCount + 1;
|
|
434
|
+
|
|
435
|
+
// Update review state with incremented retry count
|
|
436
|
+
if (persisted) {
|
|
437
|
+
const updatedState = { ...persisted, metadata };
|
|
438
|
+
writeReviewStateFn(slug, updatedState as any, worktree);
|
|
439
|
+
} else {
|
|
440
|
+
writeReviewStateFn(slug, { metadata } as any, worktree);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Transition task back to active (implementer phase) without consuming reviewer cycle
|
|
444
|
+
transitionTaskFn(slug, 'active', { rootDir: worktree, log });
|
|
445
|
+
log(fmt.status('INFO', `Auto-bouncing to implementer (${implementer}) with fix prompt. Retry ${retryCount + 1}/${MAX_GATE_RETRY}.`));
|
|
446
|
+
|
|
447
|
+
// Launch implementer with the fix prompt
|
|
448
|
+
try {
|
|
449
|
+
const launchResult = await startAgentFn('act-on-review', {
|
|
450
|
+
agent: implementer,
|
|
451
|
+
prompt: (_actualImplementer: string) => fixPrompt,
|
|
452
|
+
worktree,
|
|
453
|
+
slug,
|
|
454
|
+
role: 'implementer',
|
|
455
|
+
exclude: [],
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// Apply any agent fallback if needed
|
|
459
|
+
implementer = applyAgentFallbackFn({
|
|
460
|
+
role: 'implementer',
|
|
461
|
+
original: implementer,
|
|
462
|
+
launchResult,
|
|
463
|
+
state: persisted || {},
|
|
464
|
+
slug,
|
|
465
|
+
worktree,
|
|
466
|
+
taskResolution,
|
|
467
|
+
log,
|
|
468
|
+
writeReviewStateFn,
|
|
469
|
+
enforceTaskAssigneeFn,
|
|
470
|
+
});
|
|
471
|
+
} catch (err: unknown) {
|
|
472
|
+
error(fmt.status('FAIL', `Could not relaunch implementer (${implementer}) for gate failure auto-bounce: ${(err as Error).message}`));
|
|
473
|
+
return { bounced: false, stranded: true };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
log(fmt.status('INFO', `Implementer (${implementer}) relaunched with gate failure fix prompt.`));
|
|
477
|
+
return { bounced: true, stranded: false };
|
|
478
|
+
}
|
|
479
|
+
|
|
240
480
|
// ============================================================================
|
|
241
481
|
// Main Review Loop
|
|
242
482
|
// ============================================================================
|
|
@@ -267,12 +507,12 @@ export async function startReviewLoop(slug: string, opts: {
|
|
|
267
507
|
buildAutonomousReviewMatrixFn?: typeof buildAutonomousReviewMatrix;
|
|
268
508
|
formatMatrixSummaryFn?: typeof formatMatrixSummary;
|
|
269
509
|
selectAgentFn?: typeof selectAgent;
|
|
270
|
-
providerAvailableFn?: ((
|
|
510
|
+
providerAvailableFn?: ((_url: string) => Promise<boolean>) | null | undefined;
|
|
271
511
|
runFn?: typeof run;
|
|
272
512
|
getPrStatusFn?: typeof getPrStatus;
|
|
273
513
|
enforceTaskAssigneeFn?: typeof enforceTaskAssignee;
|
|
274
514
|
resolveReviewUserFn?: (() => string) | null | undefined;
|
|
275
|
-
forgejoAvailableFn?: ((
|
|
515
|
+
forgejoAvailableFn?: ((_url: string) => Promise<boolean>) | null;
|
|
276
516
|
resolveForgejoUserFn?: (() => string) | null;
|
|
277
517
|
readTokenFn?: typeof readToken;
|
|
278
518
|
getCommentsFn?: typeof getComments;
|
|
@@ -291,18 +531,20 @@ export async function startReviewLoop(slug: string, opts: {
|
|
|
291
531
|
consumeImplementerArtifactsFn?: typeof consumeImplementerArtifacts;
|
|
292
532
|
rebaseBeforeReviewRoundFn?: typeof rebaseBeforeReviewRound;
|
|
293
533
|
eligibleAgentsForStepFn?: typeof eligibleAgentsForStep;
|
|
294
|
-
performHandoffFn?: (
|
|
295
|
-
log?: (
|
|
296
|
-
error?: (
|
|
534
|
+
performHandoffFn?: (_slug: string, _opts?: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
535
|
+
log?: (_msg: string) => void;
|
|
536
|
+
error?: (_msg: string) => void;
|
|
297
537
|
getLatestReviewForPrFn?: typeof getLatestReviewForPr;
|
|
298
538
|
getLatestDispositionForPrFn?: typeof getLatestDispositionForPr;
|
|
299
539
|
sleepFn?: typeof delay;
|
|
300
|
-
exit?: (
|
|
540
|
+
exit?: (_code: number) => never;
|
|
301
541
|
gitFn?: typeof git;
|
|
302
|
-
isReviewProviderEnabledFn?: ((
|
|
303
|
-
legacyIsForgejoReviewEnabledFn?: ((
|
|
304
|
-
isForgejoReviewEnabledFn?: ((
|
|
305
|
-
recordStageStatsSafeFn?: (...
|
|
542
|
+
isReviewProviderEnabledFn?: ((_rootDir?: string) => boolean) | null | undefined;
|
|
543
|
+
legacyIsForgejoReviewEnabledFn?: ((_rootDir?: string) => boolean) | null;
|
|
544
|
+
isForgejoReviewEnabledFn?: ((_rootDir?: string) => boolean) | null;
|
|
545
|
+
recordStageStatsSafeFn?: (..._args: any[]) => void;
|
|
546
|
+
runPreReviewGateFn?: typeof runPreReviewGate;
|
|
547
|
+
handleGateFailureAutoBounceFn?: typeof handleGateFailureAutoBounce;
|
|
306
548
|
} = {}): Promise<void> {
|
|
307
549
|
let {
|
|
308
550
|
implementer,
|
|
@@ -317,6 +559,8 @@ export async function startReviewLoop(slug: string, opts: {
|
|
|
317
559
|
pollTimeoutSeconds = null,
|
|
318
560
|
worktree: callerWorktree,
|
|
319
561
|
missionPath,
|
|
562
|
+
runPreReviewGateFn = runPreReviewGate,
|
|
563
|
+
handleGateFailureAutoBounceFn = handleGateFailureAutoBounce,
|
|
320
564
|
resetReviewStateFn = resetReviewState,
|
|
321
565
|
maybeUpdateGraphifyBeforeReviewFn = maybeUpdateGraphifyBeforeReview,
|
|
322
566
|
readReviewStateFn = readReviewState,
|
|
@@ -741,6 +985,30 @@ export async function startReviewLoop(slug: string, opts: {
|
|
|
741
985
|
state.advanceRound();
|
|
742
986
|
}
|
|
743
987
|
|
|
988
|
+
// Snapshot the primary branch's HEAD commit for this round. This is a
|
|
989
|
+
// fallback value only, used for paths that don't rebase this round (a
|
|
990
|
+
// dry run, or resuming with an existing reviewState): once
|
|
991
|
+
// rebaseBeforeReviewRoundFn runs below, HEAD is rebased onto primary's
|
|
992
|
+
// *current* tip, so the baseline is re-captured immediately after the
|
|
993
|
+
// rebase completes (see below). Capturing it here, before the rebase,
|
|
994
|
+
// would pin the diff to a stale pre-rebase SHA; since rebase replays
|
|
995
|
+
// primary's newer commits into HEAD's ancestry, diffing against that
|
|
996
|
+
// stale SHA would surface exactly the "not rebased to main" noise this
|
|
997
|
+
// baseline exists to suppress (task-1407).
|
|
998
|
+
// Falls back to undefined (letting the prompt builders resolve the live
|
|
999
|
+
// primary branch ref themselves) if the primary branch cannot be detected,
|
|
1000
|
+
// e.g. in a repo with no main/master branch yet.
|
|
1001
|
+
const captureReviewBaseline = (): string | undefined => {
|
|
1002
|
+
try {
|
|
1003
|
+
const primaryBranchName = getPrimaryBranch(worktree, gitFn);
|
|
1004
|
+
const reviewBaselineResult = gitFn(['-C', worktree, 'rev-parse', primaryBranchName]);
|
|
1005
|
+
return (reviewBaselineResult.stdout || '').trim() || primaryBranchName;
|
|
1006
|
+
} catch {
|
|
1007
|
+
return undefined;
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
let reviewBaseline: string | undefined = captureReviewBaseline();
|
|
1011
|
+
|
|
744
1012
|
let reviewState: unknown;
|
|
745
1013
|
|
|
746
1014
|
if (state.phase === 'reviewing') {
|
|
@@ -770,7 +1038,7 @@ export async function startReviewLoop(slug: string, opts: {
|
|
|
770
1038
|
log(fmt.status('INFO', `Round ${attempt}: reviewer identity is autonomous; skipping dry-run reviewer prompt and using local review artifacts only.`));
|
|
771
1039
|
} else {
|
|
772
1040
|
log(`\n--- DRY-RUN: reviewer (${reviewer}) prompt ---`);
|
|
773
|
-
log((buildReviewPromptFn as any)({ reviewer: reviewer!, branch, implementer: implementer!, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer: '{{AGENT_NAME}}' }));
|
|
1041
|
+
log((buildReviewPromptFn as any)({ reviewer: reviewer!, branch, implementer: implementer!, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer: '{{AGENT_NAME}}', reviewBaseline }));
|
|
774
1042
|
}
|
|
775
1043
|
}
|
|
776
1044
|
} else {
|
|
@@ -784,9 +1052,57 @@ export async function startReviewLoop(slug: string, opts: {
|
|
|
784
1052
|
});
|
|
785
1053
|
if (!rebaseResult.ok) { exit(1); return; }
|
|
786
1054
|
|
|
1055
|
+
// Re-capture after rebase: HEAD is now rebased onto primary's tip,
|
|
1056
|
+
// so the pre-rebase snapshot above is stale and must be replaced
|
|
1057
|
+
// with the SHA that HEAD was actually rebased onto (task-1407).
|
|
1058
|
+
reviewBaseline = captureReviewBaseline();
|
|
1059
|
+
|
|
787
1060
|
if (!dryRun) { transitionTaskFn(slug, 'review', { rootDir: worktree, log }); }
|
|
788
1061
|
state.phase = 'reviewing';
|
|
789
1062
|
writeReviewStateFn(slug, state, worktree);
|
|
1063
|
+
|
|
1064
|
+
// Pre-review gate enforcement (ADR 0048 Control C1 / TASK-1385):
|
|
1065
|
+
// Run the verification gate before the reviewer launches. On failure,
|
|
1066
|
+
// auto-bounce to the implementer with captured gate output as fix prompt.
|
|
1067
|
+
// No reviewer cycle is consumed when the gate fails and auto-bounce occurs.
|
|
1068
|
+
if (!dryRun) {
|
|
1069
|
+
const preReviewGateResult = await runPreReviewGateFn(slug, worktree, {
|
|
1070
|
+
runFn: runFn as any,
|
|
1071
|
+
log,
|
|
1072
|
+
error,
|
|
1073
|
+
});
|
|
1074
|
+
if (!preReviewGateResult.ok) {
|
|
1075
|
+
log(fmt.status('WARN', `Pre-review gate failed for area "${preReviewGateResult.area}" (exit ${preReviewGateResult.exitCode}). Auto-bouncing to implementer.`));
|
|
1076
|
+
const bounceResult = await handleGateFailureAutoBounceFn(slug, worktree, preReviewGateResult, implementer, {
|
|
1077
|
+
startAgentFn: startAgentFn,
|
|
1078
|
+
writeReviewStateFn: writeReviewStateFn,
|
|
1079
|
+
readReviewStateFn: readReviewStateFn,
|
|
1080
|
+
transitionTaskFn: transitionTaskFn,
|
|
1081
|
+
applyAgentFallbackFn: applyAgentFallbackFn,
|
|
1082
|
+
taskResolution,
|
|
1083
|
+
enforceTaskAssigneeFn,
|
|
1084
|
+
log,
|
|
1085
|
+
error,
|
|
1086
|
+
sleepFn,
|
|
1087
|
+
buildCompactActOnReviewPromptFn,
|
|
1088
|
+
isForgejoReviewEnabledFn,
|
|
1089
|
+
isReviewProviderEnabledFn,
|
|
1090
|
+
legacyIsForgejoReviewEnabledFn,
|
|
1091
|
+
exit,
|
|
1092
|
+
});
|
|
1093
|
+
if (bounceResult.stranded) {
|
|
1094
|
+
error(fmt.status('FAIL', `Pre-review gate failure stranded mission ${slug}. Exiting review loop.`));
|
|
1095
|
+
exit(1); return;
|
|
1096
|
+
}
|
|
1097
|
+
if (bounceResult.bounced) {
|
|
1098
|
+
log(fmt.status('INFO', `Auto-bounce succeeded. Continuing to next round.`));
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
} else {
|
|
1102
|
+
log(fmt.status('PASS', `Pre-review gate passed for area "${preReviewGateResult.area}".`));
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
790
1106
|
if (reviewer === 'autonomous' && !forgejoEnabled) {
|
|
791
1107
|
log(fmt.status('INFO', `Round ${attempt}: reviewer identity is autonomous; skipping reviewer launch and using local review artifacts only.`));
|
|
792
1108
|
} else {
|
|
@@ -796,7 +1112,7 @@ export async function startReviewLoop(slug: string, opts: {
|
|
|
796
1112
|
try {
|
|
797
1113
|
reviewerLaunchResult = await startAgentFn('review', {
|
|
798
1114
|
agent: reviewer,
|
|
799
|
-
prompt: (actualReviewer: string) => (buildCompactReviewPromptFn as any)({ reviewer: reviewer!, branch, implementer: implementer!, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer }),
|
|
1115
|
+
prompt: (actualReviewer: string) => (buildCompactReviewPromptFn as any)({ reviewer: reviewer!, branch, implementer: implementer!, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer, reviewBaseline }),
|
|
800
1116
|
worktree, slug, role: 'reviewer', exclude: [implementer]
|
|
801
1117
|
});
|
|
802
1118
|
} catch (err: unknown) {
|
|
@@ -861,7 +1177,7 @@ export async function startReviewLoop(slug: string, opts: {
|
|
|
861
1177
|
try {
|
|
862
1178
|
relaunchResult = await startAgentFn('review', {
|
|
863
1179
|
agent: reviewer,
|
|
864
|
-
prompt: (actualReviewer: string) => (buildCompactReviewPromptFn as any)({ reviewer: reviewer!, branch, implementer: implementer!, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer }) + '\n\n' + recoveryPrompt,
|
|
1180
|
+
prompt: (actualReviewer: string) => (buildCompactReviewPromptFn as any)({ reviewer: reviewer!, branch, implementer: implementer!, focus, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualReviewer, reviewBaseline }) + '\n\n' + recoveryPrompt,
|
|
865
1181
|
worktree, slug, role: 'reviewer', exclude: [implementer]
|
|
866
1182
|
});
|
|
867
1183
|
} catch (err: unknown) {
|
|
@@ -998,7 +1314,7 @@ export async function startReviewLoop(slug: string, opts: {
|
|
|
998
1314
|
// First launch or re-launch after stale BLOCKED/PARKED
|
|
999
1315
|
if (dryRun) {
|
|
1000
1316
|
log(`\n--- DRY-RUN: implementer (${implementer}) act-on-review prompt ---`);
|
|
1001
|
-
log((buildActOnReviewPromptFn as any)({ implementer: implementer!, branch, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer: '{{AGENT_NAME}}' }));
|
|
1317
|
+
log((buildActOnReviewPromptFn as any)({ implementer: implementer!, branch, attempt, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer: '{{AGENT_NAME}}', reviewBaseline }));
|
|
1002
1318
|
if (reLaunch!) {
|
|
1003
1319
|
log(fmt.status('INFO', `Round ${attempt}: stale BLOCKED/PARKED disposition replaced by fresh implementer action.`));
|
|
1004
1320
|
}
|
|
@@ -1016,7 +1332,7 @@ export async function startReviewLoop(slug: string, opts: {
|
|
|
1016
1332
|
try {
|
|
1017
1333
|
implementerLaunchResult = await startAgentFn('act-on-review', {
|
|
1018
1334
|
agent: implementer,
|
|
1019
|
-
prompt: (actualImplementer: string) => (buildCompactActOnReviewPromptFn as any)({ implementer: implementer!, branch, attempt, reviewOutcome: reviewState, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer }),
|
|
1335
|
+
prompt: (actualImplementer: string) => (buildCompactActOnReviewPromptFn as any)({ implementer: implementer!, branch, attempt, reviewOutcome: reviewState, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer, reviewBaseline }),
|
|
1020
1336
|
worktree, slug, role: 'implementer', exclude: [reviewer]
|
|
1021
1337
|
});
|
|
1022
1338
|
} catch (err: unknown) {
|
|
@@ -1078,7 +1394,7 @@ export async function startReviewLoop(slug: string, opts: {
|
|
|
1078
1394
|
try {
|
|
1079
1395
|
relaunchResult = await startAgentFn('act-on-review', {
|
|
1080
1396
|
agent: implementer,
|
|
1081
|
-
prompt: (actualImplementer: string) => (buildCompactActOnReviewPromptFn as any)({ implementer: implementer!, branch, attempt, reviewOutcome: reviewState, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer }) + '\n\n' + recoveryPrompt,
|
|
1397
|
+
prompt: (actualImplementer: string) => (buildCompactActOnReviewPromptFn as any)({ implementer: implementer!, branch, attempt, reviewOutcome: reviewState, repoRoot: worktree, missionPath: effectiveMissionPath || undefined, actualImplementer, reviewBaseline }) + '\n\n' + recoveryPrompt,
|
|
1082
1398
|
worktree, slug, role: 'implementer', exclude: [reviewer]
|
|
1083
1399
|
});
|
|
1084
1400
|
} catch (err: unknown) {
|
|
@@ -64,14 +64,14 @@ async function pollForReview(
|
|
|
64
64
|
sinceIso: string,
|
|
65
65
|
token: string,
|
|
66
66
|
options: {
|
|
67
|
-
getLatestReviewForPrFn?: (
|
|
68
|
-
sleepFn?: (
|
|
67
|
+
getLatestReviewForPrFn?: (_prNumber: number, _reviewerUser: string, _sinceIso: string, _token: string) => Promise<unknown>;
|
|
68
|
+
sleepFn?: (_ms: number) => Promise<void>;
|
|
69
69
|
intervalMs?: number;
|
|
70
70
|
timeoutMs?: number;
|
|
71
71
|
verbose?: boolean;
|
|
72
72
|
label?: string;
|
|
73
73
|
retryCount?: number;
|
|
74
|
-
log?: (
|
|
74
|
+
log?: (_msg: string) => void;
|
|
75
75
|
} = {}
|
|
76
76
|
): Promise<string | typeof POLL_TIMEOUT | null> {
|
|
77
77
|
const {
|
|
@@ -131,14 +131,14 @@ async function pollForDisposition(
|
|
|
131
131
|
sinceIso: string,
|
|
132
132
|
token: string,
|
|
133
133
|
options: {
|
|
134
|
-
getLatestDispositionForPrFn?: (
|
|
135
|
-
sleepFn?: (
|
|
134
|
+
getLatestDispositionForPrFn?: (_prNumber: number, _implementerUser: string, _sinceIso: string, _token: string) => Promise<unknown>;
|
|
135
|
+
sleepFn?: (_ms: number) => Promise<void>;
|
|
136
136
|
intervalMs?: number;
|
|
137
137
|
timeoutMs?: number;
|
|
138
138
|
verbose?: boolean;
|
|
139
139
|
label?: string;
|
|
140
140
|
retryCount?: number;
|
|
141
|
-
log?: (
|
|
141
|
+
log?: (_msg: string) => void;
|
|
142
142
|
} = {}
|
|
143
143
|
): Promise<string | typeof POLL_TIMEOUT | null> {
|
|
144
144
|
const {
|
|
@@ -111,7 +111,7 @@ function resolvePrimaryBranch(repoRoot) {
|
|
|
111
111
|
* @param {{reviewer: string, branch: string, implementer: string, focus?: string, attempt: number, repoRoot?: string, missionPath?: string}} opts
|
|
112
112
|
* @returns {string}
|
|
113
113
|
*/
|
|
114
|
-
function buildReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, repoRoot = '', missionPath: missionPathOverride }) {
|
|
114
|
+
function buildReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, repoRoot = '', missionPath: missionPathOverride, reviewBaseline }) {
|
|
115
115
|
const entrypoint = reviewEntrypoint(reviewer);
|
|
116
116
|
const repoLine = repoRoot ? `\noperate from repo root: ${repoRoot}` : '';
|
|
117
117
|
const slug = branch.replace(/^mission\//, '');
|
|
@@ -129,6 +129,7 @@ function buildReviewPrompt({ reviewer, branch, implementer, focus = 'all', attem
|
|
|
129
129
|
.replaceAll('{{missionPath}}', missionPath)
|
|
130
130
|
.replaceAll('{{artifactDir}}', artifactDir)
|
|
131
131
|
.replaceAll('{{primaryBranch}}', resolvePrimaryBranch(repoRoot))
|
|
132
|
+
.replaceAll('{{reviewBaseline}}', reviewBaseline || resolvePrimaryBranch(repoRoot))
|
|
132
133
|
.replaceAll('{{review_entrypoint}}', entrypoint)
|
|
133
134
|
.replaceAll('YYYY', year)
|
|
134
135
|
.replaceAll('{{repo_line}}', repoLine ? repoLine.trim() + '\n- ' : '');
|
|
@@ -137,7 +138,7 @@ function buildReviewPrompt({ reviewer, branch, implementer, focus = 'all', attem
|
|
|
137
138
|
* @param {{implementer: string, branch: string, attempt: number, repoRoot?: string, missionPath?: string}} opts
|
|
138
139
|
* @returns {string}
|
|
139
140
|
*/
|
|
140
|
-
function buildActOnReviewPrompt({ implementer, branch, attempt, repoRoot = '', missionPath: missionPathOverride }) {
|
|
141
|
+
function buildActOnReviewPrompt({ implementer, branch, attempt, repoRoot = '', missionPath: missionPathOverride, reviewBaseline }) {
|
|
141
142
|
const entrypoint = actOnReviewEntrypoint(implementer);
|
|
142
143
|
const repoLine = repoRoot ? `\noperate from repo root: ${repoRoot}` : '';
|
|
143
144
|
const slug = branch.replace(/^mission\//, '');
|
|
@@ -153,6 +154,7 @@ function buildActOnReviewPrompt({ implementer, branch, attempt, repoRoot = '', m
|
|
|
153
154
|
.replaceAll('{{missionPath}}', missionPath)
|
|
154
155
|
.replaceAll('{{artifactDir}}', artifactDir)
|
|
155
156
|
.replaceAll('{{primaryBranch}}', resolvePrimaryBranch(repoRoot))
|
|
157
|
+
.replaceAll('{{reviewBaseline}}', reviewBaseline || resolvePrimaryBranch(repoRoot))
|
|
156
158
|
.replaceAll('{{act_on_review_entrypoint}}', entrypoint)
|
|
157
159
|
.replaceAll('YYYY', year)
|
|
158
160
|
.replaceAll('{{repo_line}}', repoLine ? repoLine.trim() + '\n- ' : '');
|
|
@@ -161,7 +163,7 @@ function buildActOnReviewPrompt({ implementer, branch, attempt, repoRoot = '', m
|
|
|
161
163
|
* @param {{reviewer: string, branch: string, implementer: string, focus?: string, attempt: number, actualReviewer?: string, repoRoot?: string, missionPath?: string}} opts
|
|
162
164
|
* @returns {string}
|
|
163
165
|
*/
|
|
164
|
-
function buildCompactReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, actualReviewer, repoRoot = '', missionPath: missionPathOverride }) {
|
|
166
|
+
function buildCompactReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, actualReviewer, repoRoot = '', missionPath: missionPathOverride, reviewBaseline }) {
|
|
165
167
|
const slug = branch.replace(/^mission\//, '');
|
|
166
168
|
const finalReviewer = actualReviewer || reviewer;
|
|
167
169
|
const year = (0, mission_utils_js_1.getMissionYear)(slug, repoRoot || process.cwd());
|
|
@@ -179,6 +181,7 @@ function buildCompactReviewPrompt({ reviewer, branch, implementer, focus = 'all'
|
|
|
179
181
|
.replaceAll('{{missionPath}}', missionPath)
|
|
180
182
|
.replaceAll('{{artifactDir}}', artifactDir)
|
|
181
183
|
.replaceAll('{{primaryBranch}}', primaryBranch)
|
|
184
|
+
.replaceAll('{{reviewBaseline}}', reviewBaseline || primaryBranch)
|
|
182
185
|
.replaceAll('YYYY', year)
|
|
183
186
|
.replaceAll('{{review_entrypoint}}', reviewEntrypoint(finalReviewer));
|
|
184
187
|
}
|
|
@@ -186,7 +189,7 @@ function buildCompactReviewPrompt({ reviewer, branch, implementer, focus = 'all'
|
|
|
186
189
|
* @param {{implementer: string, branch: string, attempt: number, reviewOutcome?: string, actualImplementer?: string, repoRoot?: string, missionPath?: string}} opts
|
|
187
190
|
* @returns {string}
|
|
188
191
|
*/
|
|
189
|
-
function buildCompactActOnReviewPrompt({ implementer, branch, attempt, reviewOutcome = '?', actualImplementer, repoRoot = '', missionPath: missionPathOverride }) {
|
|
192
|
+
function buildCompactActOnReviewPrompt({ implementer, branch, attempt, reviewOutcome = '?', actualImplementer, repoRoot = '', missionPath: missionPathOverride, reviewBaseline }) {
|
|
190
193
|
const slug = branch.replace(/^mission\//, '');
|
|
191
194
|
const finalImplementer = actualImplementer || implementer;
|
|
192
195
|
const year = (0, mission_utils_js_1.getMissionYear)(slug, repoRoot || process.cwd());
|
|
@@ -202,6 +205,7 @@ function buildCompactActOnReviewPrompt({ implementer, branch, attempt, reviewOut
|
|
|
202
205
|
.replaceAll('{{missionPath}}', missionPath)
|
|
203
206
|
.replaceAll('{{artifactDir}}', artifactDir)
|
|
204
207
|
.replaceAll('{{primaryBranch}}', primaryBranch)
|
|
208
|
+
.replaceAll('{{reviewBaseline}}', reviewBaseline || primaryBranch)
|
|
205
209
|
.replaceAll('{{review_outcome}}', reviewOutcome)
|
|
206
210
|
.replaceAll('YYYY', year)
|
|
207
211
|
.replaceAll('{{act_on_review_entrypoint}}', actOnReviewEntrypoint(finalImplementer));
|
|
@@ -72,8 +72,8 @@ function resolvePrimaryBranch(repoRoot?: string): string {
|
|
|
72
72
|
* @param {{reviewer: string, branch: string, implementer: string, focus?: string, attempt: number, repoRoot?: string, missionPath?: string}} opts
|
|
73
73
|
* @returns {string}
|
|
74
74
|
*/
|
|
75
|
-
export function buildReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, repoRoot = '', missionPath: missionPathOverride }: {
|
|
76
|
-
reviewer: string; branch: string; implementer: string; focus?: string; attempt: number; repoRoot?: string; missionPath?: string;
|
|
75
|
+
export function buildReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, repoRoot = '', missionPath: missionPathOverride, reviewBaseline }: {
|
|
76
|
+
reviewer: string; branch: string; implementer: string; focus?: string; attempt: number; repoRoot?: string; missionPath?: string; reviewBaseline?: string;
|
|
77
77
|
}): string {
|
|
78
78
|
const entrypoint = reviewEntrypoint(reviewer);
|
|
79
79
|
const repoLine = repoRoot ? `\noperate from repo root: ${repoRoot}` : '';
|
|
@@ -93,6 +93,7 @@ export function buildReviewPrompt({ reviewer, branch, implementer, focus = 'all'
|
|
|
93
93
|
.replaceAll('{{missionPath}}', missionPath)
|
|
94
94
|
.replaceAll('{{artifactDir}}', artifactDir)
|
|
95
95
|
.replaceAll('{{primaryBranch}}', resolvePrimaryBranch(repoRoot))
|
|
96
|
+
.replaceAll('{{reviewBaseline}}', reviewBaseline || resolvePrimaryBranch(repoRoot))
|
|
96
97
|
.replaceAll('{{review_entrypoint}}', entrypoint)
|
|
97
98
|
.replaceAll('YYYY', year)
|
|
98
99
|
.replaceAll('{{repo_line}}', repoLine ? repoLine.trim() + '\n- ' : '');
|
|
@@ -102,8 +103,8 @@ export function buildReviewPrompt({ reviewer, branch, implementer, focus = 'all'
|
|
|
102
103
|
* @param {{implementer: string, branch: string, attempt: number, repoRoot?: string, missionPath?: string}} opts
|
|
103
104
|
* @returns {string}
|
|
104
105
|
*/
|
|
105
|
-
export function buildActOnReviewPrompt({ implementer, branch, attempt, repoRoot = '', missionPath: missionPathOverride }: {
|
|
106
|
-
implementer: string; branch: string; attempt: number; repoRoot?: string; missionPath?: string;
|
|
106
|
+
export function buildActOnReviewPrompt({ implementer, branch, attempt, repoRoot = '', missionPath: missionPathOverride, reviewBaseline }: {
|
|
107
|
+
implementer: string; branch: string; attempt: number; repoRoot?: string; missionPath?: string; reviewBaseline?: string;
|
|
107
108
|
}): string {
|
|
108
109
|
const entrypoint = actOnReviewEntrypoint(implementer);
|
|
109
110
|
const repoLine = repoRoot ? `\noperate from repo root: ${repoRoot}` : '';
|
|
@@ -121,6 +122,7 @@ export function buildActOnReviewPrompt({ implementer, branch, attempt, repoRoot
|
|
|
121
122
|
.replaceAll('{{missionPath}}', missionPath)
|
|
122
123
|
.replaceAll('{{artifactDir}}', artifactDir)
|
|
123
124
|
.replaceAll('{{primaryBranch}}', resolvePrimaryBranch(repoRoot))
|
|
125
|
+
.replaceAll('{{reviewBaseline}}', reviewBaseline || resolvePrimaryBranch(repoRoot))
|
|
124
126
|
.replaceAll('{{act_on_review_entrypoint}}', entrypoint)
|
|
125
127
|
.replaceAll('YYYY', year)
|
|
126
128
|
.replaceAll('{{repo_line}}', repoLine ? repoLine.trim() + '\n- ' : '');
|
|
@@ -130,8 +132,8 @@ export function buildActOnReviewPrompt({ implementer, branch, attempt, repoRoot
|
|
|
130
132
|
* @param {{reviewer: string, branch: string, implementer: string, focus?: string, attempt: number, actualReviewer?: string, repoRoot?: string, missionPath?: string}} opts
|
|
131
133
|
* @returns {string}
|
|
132
134
|
*/
|
|
133
|
-
export function buildCompactReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, actualReviewer, repoRoot = '', missionPath: missionPathOverride }: {
|
|
134
|
-
reviewer: string; branch: string; implementer: string; focus?: string; attempt: number; actualReviewer?: string; repoRoot?: string; missionPath?: string;
|
|
135
|
+
export function buildCompactReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, actualReviewer, repoRoot = '', missionPath: missionPathOverride, reviewBaseline }: {
|
|
136
|
+
reviewer: string; branch: string; implementer: string; focus?: string; attempt: number; actualReviewer?: string; repoRoot?: string; missionPath?: string; reviewBaseline?: string;
|
|
135
137
|
}): string {
|
|
136
138
|
const slug = branch.replace(/^mission\//, '');
|
|
137
139
|
const finalReviewer = actualReviewer || reviewer;
|
|
@@ -150,6 +152,7 @@ export function buildCompactReviewPrompt({ reviewer, branch, implementer, focus
|
|
|
150
152
|
.replaceAll('{{missionPath}}', missionPath)
|
|
151
153
|
.replaceAll('{{artifactDir}}', artifactDir)
|
|
152
154
|
.replaceAll('{{primaryBranch}}', primaryBranch)
|
|
155
|
+
.replaceAll('{{reviewBaseline}}', reviewBaseline || primaryBranch)
|
|
153
156
|
.replaceAll('YYYY', year)
|
|
154
157
|
.replaceAll('{{review_entrypoint}}', reviewEntrypoint(finalReviewer));
|
|
155
158
|
}
|
|
@@ -158,8 +161,8 @@ export function buildCompactReviewPrompt({ reviewer, branch, implementer, focus
|
|
|
158
161
|
* @param {{implementer: string, branch: string, attempt: number, reviewOutcome?: string, actualImplementer?: string, repoRoot?: string, missionPath?: string}} opts
|
|
159
162
|
* @returns {string}
|
|
160
163
|
*/
|
|
161
|
-
export function buildCompactActOnReviewPrompt({ implementer, branch, attempt, reviewOutcome = '?', actualImplementer, repoRoot = '', missionPath: missionPathOverride }: {
|
|
162
|
-
implementer: string; branch: string; attempt: number; reviewOutcome?: string; actualImplementer?: string; repoRoot?: string; missionPath?: string;
|
|
164
|
+
export function buildCompactActOnReviewPrompt({ implementer, branch, attempt, reviewOutcome = '?', actualImplementer, repoRoot = '', missionPath: missionPathOverride, reviewBaseline }: {
|
|
165
|
+
implementer: string; branch: string; attempt: number; reviewOutcome?: string; actualImplementer?: string; repoRoot?: string; missionPath?: string; reviewBaseline?: string;
|
|
163
166
|
}): string {
|
|
164
167
|
const slug = branch.replace(/^mission\//, '');
|
|
165
168
|
const finalImplementer = actualImplementer || implementer;
|
|
@@ -176,6 +179,7 @@ export function buildCompactActOnReviewPrompt({ implementer, branch, attempt, re
|
|
|
176
179
|
.replaceAll('{{missionPath}}', missionPath)
|
|
177
180
|
.replaceAll('{{artifactDir}}', artifactDir)
|
|
178
181
|
.replaceAll('{{primaryBranch}}', primaryBranch)
|
|
182
|
+
.replaceAll('{{reviewBaseline}}', reviewBaseline || primaryBranch)
|
|
179
183
|
.replaceAll('{{review_outcome}}', reviewOutcome)
|
|
180
184
|
.replaceAll('YYYY', year)
|
|
181
185
|
.replaceAll('{{act_on_review_entrypoint}}', actOnReviewEntrypoint(finalImplementer));
|