@effect-agent/pr-review 0.1.0-beta.23 → 0.1.0-beta.24
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/README.md +12 -5
- package/dist/action.d.mts +3 -3
- package/dist/action.mjs +11 -2
- package/dist/action.mjs.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/{fan-out-BJBTAYuh.d.mts → fan-out-Bi1v0VaU.d.mts} +56 -4
- package/dist/{github-BbwYzNrC.mjs → github-C6jrBLA2.mjs} +222 -60
- package/dist/github-C6jrBLA2.mjs.map +1 -0
- package/dist/index.d.mts +17 -15
- package/dist/index.mjs +3 -3
- package/dist/{providers-NyP-4rS6.mjs → providers-2Jao2ZAX.mjs} +14 -5
- package/dist/providers-2Jao2ZAX.mjs.map +1 -0
- package/dist/testing.d.mts +3 -1
- package/dist/testing.mjs +3 -2
- package/dist/testing.mjs.map +1 -1
- package/package.json +2 -2
- package/src/action.ts +18 -0
- package/src/internal/factory.ts +4 -4
- package/src/internal/fan-out.ts +152 -10
- package/src/internal/fingerprint.ts +16 -10
- package/src/internal/fixtures.ts +6 -0
- package/src/internal/github.ts +36 -7
- package/src/internal/review-state.ts +181 -90
- package/src/internal/run.ts +20 -0
- package/dist/github-BbwYzNrC.mjs.map +0 -1
- package/dist/providers-NyP-4rS6.mjs.map +0 -1
|
@@ -49,6 +49,21 @@ export class StoredReviewConcern extends Schema.Class<StoredReviewConcern>(
|
|
|
49
49
|
/** The carried-scope bound; a run that cannot fit its leftovers publishes no state. */
|
|
50
50
|
export const MAX_STORED_UNREVIEWED_PATHS = 100;
|
|
51
51
|
|
|
52
|
+
/** Failed-pass records stored beside the leftover paths; one per unit stage. */
|
|
53
|
+
export const MAX_STORED_UNREVIEWED_PASSES = 24;
|
|
54
|
+
|
|
55
|
+
/** Stages a leftover path may need retried without a second general discovery. */
|
|
56
|
+
export const UnreviewedStage = Schema.Literals(["discovery", "specialist", "verification"]);
|
|
57
|
+
export type UnreviewedStage = typeof UnreviewedStage.Type;
|
|
58
|
+
|
|
59
|
+
/** One failed fan-out pass whose paths should be retried, not rediscovered. */
|
|
60
|
+
export class StoredUnreviewedPass extends Schema.Class<StoredUnreviewedPass>(
|
|
61
|
+
"@effect-agent/pr-review/StoredUnreviewedPass",
|
|
62
|
+
)({
|
|
63
|
+
stage: UnreviewedStage,
|
|
64
|
+
paths: Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12)),
|
|
65
|
+
}) {}
|
|
66
|
+
|
|
52
67
|
/**
|
|
53
68
|
* Versioned state embedded after EVERY completed run that can be signed. The
|
|
54
69
|
* head plus full-scope fingerprint forms an incremental baseline; an absent
|
|
@@ -75,6 +90,14 @@ export class ReviewState extends Schema.Class<ReviewState>("@effect-agent/pr-rev
|
|
|
75
90
|
unresolvedConcerns: Schema.Array(StoredReviewConcern).check(Schema.isMaxLength(10)),
|
|
76
91
|
/** Retryable review gaps carried into the next incremental run's scope. */
|
|
77
92
|
unreviewedPaths: Schema.Array(ChangedPath).check(Schema.isMaxLength(MAX_STORED_UNREVIEWED_PATHS)),
|
|
93
|
+
/**
|
|
94
|
+
* Which failed pass produced those leftovers. Absent on state-v2 markers
|
|
95
|
+
* written before this field existed; those leftovers still re-enter scope
|
|
96
|
+
* but cannot skip rediscovery. Present (including empty) on new markers.
|
|
97
|
+
*/
|
|
98
|
+
unreviewedPasses: Schema.optionalKey(
|
|
99
|
+
Schema.Array(StoredUnreviewedPass).check(Schema.isMaxLength(MAX_STORED_UNREVIEWED_PASSES)),
|
|
100
|
+
),
|
|
78
101
|
/**
|
|
79
102
|
* True only when the producing run had complete input coverage, no
|
|
80
103
|
* unsettled pass, and nothing carried. Skip-unchanged authority: an
|
|
@@ -292,6 +315,12 @@ export interface ReviewSelection {
|
|
|
292
315
|
readonly files: ReadonlyArray<ChangedFile>;
|
|
293
316
|
/** Paths whose changes invalidate prior findings, including paths reverted out of the PR. */
|
|
294
317
|
readonly affectedPaths: ReadonlyArray<string>;
|
|
318
|
+
/**
|
|
319
|
+
* Leftover paths whose contents did not change. Fan-out retries only the
|
|
320
|
+
* recorded failed stages on these paths and keeps their stored findings.
|
|
321
|
+
*/
|
|
322
|
+
readonly retryPaths: ReadonlyArray<string>;
|
|
323
|
+
readonly retryStages: ReadonlyArray<UnreviewedStage>;
|
|
295
324
|
readonly totalFiles: number;
|
|
296
325
|
readonly baselineSha: string | undefined;
|
|
297
326
|
readonly priorState: ReviewState | undefined;
|
|
@@ -312,12 +341,26 @@ const fullSelection = (input: {
|
|
|
312
341
|
affectedPaths: input.files.flatMap((file) =>
|
|
313
342
|
file.previousPath === undefined ? [file.path] : [file.path, file.previousPath],
|
|
314
343
|
),
|
|
344
|
+
retryPaths: [],
|
|
345
|
+
retryStages: [],
|
|
315
346
|
totalFiles: input.totalFiles,
|
|
316
347
|
baselineSha: undefined,
|
|
317
348
|
priorState: undefined,
|
|
318
349
|
profileFingerprint: input.profileFingerprint,
|
|
319
350
|
});
|
|
320
351
|
|
|
352
|
+
/** Three-dot lineage from the reviewed head to the current head is usable. */
|
|
353
|
+
export const isLineageAncestor = (
|
|
354
|
+
comparison: ReviewHeadComparison,
|
|
355
|
+
priorState: ReviewState,
|
|
356
|
+
currentHeadSha: string,
|
|
357
|
+
): boolean =>
|
|
358
|
+
comparison.baseSha === priorState.reviewedHeadSha &&
|
|
359
|
+
comparison.headSha === currentHeadSha &&
|
|
360
|
+
comparison.mergeBaseSha === priorState.reviewedHeadSha &&
|
|
361
|
+
!comparison.truncated &&
|
|
362
|
+
(comparison.status === "ahead" || comparison.status === "identical");
|
|
363
|
+
|
|
321
364
|
/**
|
|
322
365
|
* Validate that persisted state belongs to this exact PR/base lineage and the
|
|
323
366
|
* same review profile. A mismatch is a full-review reason, never an error that
|
|
@@ -340,6 +383,96 @@ export const validateReviewState = (
|
|
|
340
383
|
return undefined;
|
|
341
384
|
};
|
|
342
385
|
|
|
386
|
+
const filePaths = (file: ChangedFile): ReadonlyArray<string> =>
|
|
387
|
+
file.previousPath === undefined ? [file.path] : [file.path, file.previousPath];
|
|
388
|
+
|
|
389
|
+
const incrementalFromDelta = (input: {
|
|
390
|
+
readonly current: PullRequestMetadata;
|
|
391
|
+
readonly fullFiles: ReadonlyArray<ChangedFile>;
|
|
392
|
+
readonly profileFingerprint: string;
|
|
393
|
+
readonly priorState: ReviewState;
|
|
394
|
+
readonly deltaFiles: ReadonlyArray<ChangedFile>;
|
|
395
|
+
readonly extraAffectedPaths?: ReadonlyArray<string> | undefined;
|
|
396
|
+
readonly reason: string;
|
|
397
|
+
}): ReviewSelection => {
|
|
398
|
+
const currentPaths = new Set(input.fullFiles.flatMap(filePaths));
|
|
399
|
+
const affectedPaths = new Set([
|
|
400
|
+
...input.deltaFiles.flatMap(filePaths),
|
|
401
|
+
...(input.extraAffectedPaths ?? []),
|
|
402
|
+
]);
|
|
403
|
+
const selectedByPath = new Map<string, ChangedFile>();
|
|
404
|
+
for (const file of input.deltaFiles) {
|
|
405
|
+
if (
|
|
406
|
+
currentPaths.has(file.path) ||
|
|
407
|
+
(file.previousPath !== undefined && currentPaths.has(file.previousPath))
|
|
408
|
+
) {
|
|
409
|
+
selectedByPath.set(file.path, file);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const carriedPaths = input.priorState.unreviewedPaths.filter((path) => currentPaths.has(path));
|
|
413
|
+
const surgical = input.priorState.unreviewedPasses !== undefined;
|
|
414
|
+
const retryOnly = new Set<string>();
|
|
415
|
+
const retryStages = new Set<UnreviewedStage>();
|
|
416
|
+
for (const path of carriedPaths) {
|
|
417
|
+
if (affectedPaths.has(path)) continue;
|
|
418
|
+
retryOnly.add(path);
|
|
419
|
+
if (surgical) {
|
|
420
|
+
for (const pass of input.priorState.unreviewedPasses ?? []) {
|
|
421
|
+
if (pass.paths.includes(path)) retryStages.add(pass.stage);
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
affectedPaths.add(path);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (
|
|
428
|
+
surgical &&
|
|
429
|
+
retryStages.has("verification") &&
|
|
430
|
+
!retryStages.has("discovery") &&
|
|
431
|
+
!retryStages.has("specialist")
|
|
432
|
+
) {
|
|
433
|
+
retryStages.add("discovery");
|
|
434
|
+
retryStages.add("specialist");
|
|
435
|
+
}
|
|
436
|
+
if (surgical && retryOnly.size > 0 && retryStages.size === 0) {
|
|
437
|
+
for (const path of retryOnly) affectedPaths.add(path);
|
|
438
|
+
retryStages.add("discovery");
|
|
439
|
+
retryStages.add("specialist");
|
|
440
|
+
retryStages.add("verification");
|
|
441
|
+
}
|
|
442
|
+
if (carriedPaths.length > 0 || (input.extraAffectedPaths?.length ?? 0) > 0) {
|
|
443
|
+
for (const file of input.fullFiles) {
|
|
444
|
+
const needed =
|
|
445
|
+
affectedPaths.has(file.path) ||
|
|
446
|
+
(file.previousPath !== undefined && affectedPaths.has(file.previousPath)) ||
|
|
447
|
+
retryOnly.has(file.path) ||
|
|
448
|
+
(file.previousPath !== undefined && retryOnly.has(file.previousPath));
|
|
449
|
+
if (needed) selectedByPath.set(file.path, file);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const selectedFiles = [...selectedByPath.values()].sort((left, right) =>
|
|
453
|
+
left.path < right.path ? -1 : left.path > right.path ? 1 : 0,
|
|
454
|
+
);
|
|
455
|
+
const leftoverCount = [...retryOnly].filter((path) => !affectedPaths.has(path)).length;
|
|
456
|
+
const carriedReason =
|
|
457
|
+
leftoverCount > 0 && surgical
|
|
458
|
+
? `; retrying ${leftoverCount} unchanged leftover path(s) without rediscovery`
|
|
459
|
+
: carriedPaths.length > 0
|
|
460
|
+
? `; retrying ${carriedPaths.length} carried unreviewed path(s)`
|
|
461
|
+
: "";
|
|
462
|
+
return {
|
|
463
|
+
mode: "incremental",
|
|
464
|
+
reason: `${input.reason}${carriedReason}`,
|
|
465
|
+
files: selectedFiles,
|
|
466
|
+
affectedPaths: [...affectedPaths].sort(),
|
|
467
|
+
retryPaths: [...retryOnly].filter((path) => !affectedPaths.has(path)).sort(),
|
|
468
|
+
retryStages: [...retryStages].sort(),
|
|
469
|
+
totalFiles: selectedFiles.length,
|
|
470
|
+
baselineSha: input.priorState.reviewedHeadSha,
|
|
471
|
+
priorState: input.priorState,
|
|
472
|
+
profileFingerprint: input.profileFingerprint,
|
|
473
|
+
};
|
|
474
|
+
};
|
|
475
|
+
|
|
343
476
|
/** Pure, deterministic range selection with conservative full-review fallbacks. */
|
|
344
477
|
export const selectReviewRange = (input: {
|
|
345
478
|
readonly requestedMode: ReviewMode;
|
|
@@ -349,6 +482,12 @@ export const selectReviewRange = (input: {
|
|
|
349
482
|
readonly priorState: ReviewState | undefined;
|
|
350
483
|
readonly comparison: ReviewHeadComparison | undefined;
|
|
351
484
|
readonly baseComparison?: ReviewHeadComparison | undefined;
|
|
485
|
+
/**
|
|
486
|
+
* Two-dot tree comparison used when the reviewed head is not a git ancestor
|
|
487
|
+
* (rebase, amend, force-push). Intersected with the current PR path set so
|
|
488
|
+
* main-drift outside the pull request never re-enters scope.
|
|
489
|
+
*/
|
|
490
|
+
readonly contentComparison?: ReviewHeadComparison | undefined;
|
|
352
491
|
readonly lookupFailure?: string | undefined;
|
|
353
492
|
}): ReviewSelection => {
|
|
354
493
|
const full = (reason: string) =>
|
|
@@ -366,89 +505,53 @@ export const selectReviewRange = (input: {
|
|
|
366
505
|
const invalid = validateReviewState(input.priorState, input.current, input.profileFingerprint);
|
|
367
506
|
if (invalid !== undefined) return full(invalid);
|
|
368
507
|
const comparison = input.comparison;
|
|
369
|
-
if (comparison === undefined) return full("the incremental head comparison was unavailable");
|
|
370
508
|
if (
|
|
371
|
-
comparison
|
|
372
|
-
comparison.
|
|
373
|
-
comparison.mergeBaseSha !== input.priorState.reviewedHeadSha ||
|
|
374
|
-
(comparison.status !== "ahead" && comparison.status !== "identical")
|
|
509
|
+
comparison !== undefined &&
|
|
510
|
+
isLineageAncestor(comparison, input.priorState, input.current.headSha)
|
|
375
511
|
) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
baseComparison.
|
|
393
|
-
|
|
394
|
-
(baseComparison.status !== "ahead" && baseComparison.status !== "identical") ||
|
|
395
|
-
baseComparison.truncated
|
|
396
|
-
) {
|
|
397
|
-
return full("the pull request base changed materially or exceeded the comparison bound");
|
|
398
|
-
}
|
|
399
|
-
for (const file of baseComparison.files) {
|
|
400
|
-
affectedPaths.add(file.path);
|
|
401
|
-
if (file.previousPath !== undefined) affectedPaths.add(file.previousPath);
|
|
402
|
-
}
|
|
403
|
-
baseReason = `; base advanced from ${input.priorState.baseSha.slice(0, 7)} and overlapping PR paths were included`;
|
|
404
|
-
}
|
|
405
|
-
const currentPaths = new Set(
|
|
406
|
-
input.fullFiles.flatMap((file) =>
|
|
407
|
-
file.previousPath === undefined ? [file.path] : [file.path, file.previousPath],
|
|
408
|
-
),
|
|
409
|
-
);
|
|
410
|
-
const selectedByPath = new Map<string, ChangedFile>();
|
|
411
|
-
for (const file of comparison.files) {
|
|
412
|
-
if (
|
|
413
|
-
currentPaths.has(file.path) ||
|
|
414
|
-
(file.previousPath !== undefined && currentPaths.has(file.previousPath))
|
|
415
|
-
) {
|
|
416
|
-
selectedByPath.set(file.path, file);
|
|
512
|
+
const extraAffected: Array<string> = [];
|
|
513
|
+
let baseReason = "";
|
|
514
|
+
if (input.priorState.baseSha !== input.current.baseSha) {
|
|
515
|
+
const baseComparison = input.baseComparison;
|
|
516
|
+
if (baseComparison === undefined) {
|
|
517
|
+
return full("the pull request base changed and its lineage comparison was unavailable");
|
|
518
|
+
}
|
|
519
|
+
if (
|
|
520
|
+
baseComparison.baseSha !== input.priorState.baseSha ||
|
|
521
|
+
baseComparison.headSha !== input.current.baseSha ||
|
|
522
|
+
baseComparison.mergeBaseSha !== input.priorState.baseSha ||
|
|
523
|
+
(baseComparison.status !== "ahead" && baseComparison.status !== "identical") ||
|
|
524
|
+
baseComparison.truncated
|
|
525
|
+
) {
|
|
526
|
+
return full("the pull request base changed materially or exceeded the comparison bound");
|
|
527
|
+
}
|
|
528
|
+
for (const file of baseComparison.files) extraAffected.push(...filePaths(file));
|
|
529
|
+
baseReason = `; base advanced from ${input.priorState.baseSha.slice(0, 7)} and overlapping PR paths were included`;
|
|
417
530
|
}
|
|
531
|
+
return incrementalFromDelta({
|
|
532
|
+
current: input.current,
|
|
533
|
+
fullFiles: input.fullFiles,
|
|
534
|
+
profileFingerprint: input.profileFingerprint,
|
|
535
|
+
priorState: input.priorState,
|
|
536
|
+
deltaFiles: comparison.files,
|
|
537
|
+
extraAffectedPaths: extraAffected,
|
|
538
|
+
reason: `changes since reviewed head ${input.priorState.reviewedHeadSha.slice(0, 7)}${baseReason}`,
|
|
539
|
+
});
|
|
418
540
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
(rescuePaths &&
|
|
430
|
-
(affectedPaths.has(file.path) ||
|
|
431
|
-
(file.previousPath !== undefined && affectedPaths.has(file.previousPath)))) ||
|
|
432
|
-
carriedPaths.has(file.path) ||
|
|
433
|
-
(file.previousPath !== undefined && carriedPaths.has(file.previousPath));
|
|
434
|
-
if (affected) selectedByPath.set(file.path, file);
|
|
435
|
-
}
|
|
541
|
+
const contentComparison = input.contentComparison;
|
|
542
|
+
if (contentComparison !== undefined && !contentComparison.truncated) {
|
|
543
|
+
return incrementalFromDelta({
|
|
544
|
+
current: input.current,
|
|
545
|
+
fullFiles: input.fullFiles,
|
|
546
|
+
profileFingerprint: input.profileFingerprint,
|
|
547
|
+
priorState: input.priorState,
|
|
548
|
+
deltaFiles: contentComparison.files,
|
|
549
|
+
reason: `rewritten history; contents changed since reviewed head ${input.priorState.reviewedHeadSha.slice(0, 7)}`,
|
|
550
|
+
});
|
|
436
551
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
);
|
|
440
|
-
const carriedReason =
|
|
441
|
-
carriedPaths.size === 0 ? "" : `; retrying ${carriedPaths.size} carried unreviewed path(s)`;
|
|
442
|
-
return {
|
|
443
|
-
mode: "incremental",
|
|
444
|
-
reason: `changes since reviewed head ${input.priorState.reviewedHeadSha.slice(0, 7)}${baseReason}${carriedReason}`,
|
|
445
|
-
files: selectedFiles,
|
|
446
|
-
affectedPaths: [...affectedPaths].sort(),
|
|
447
|
-
totalFiles: selectedFiles.length,
|
|
448
|
-
baselineSha: input.priorState.reviewedHeadSha,
|
|
449
|
-
priorState: input.priorState,
|
|
450
|
-
profileFingerprint: input.profileFingerprint,
|
|
451
|
-
};
|
|
552
|
+
if (comparison === undefined) return full("the incremental head comparison was unavailable");
|
|
553
|
+
if (comparison.truncated) return full("the incremental comparison exceeded GitHub's file bound");
|
|
554
|
+
return full("the prior reviewed head is not an ancestor of the current head");
|
|
452
555
|
};
|
|
453
556
|
|
|
454
557
|
/** Per-run context consumed by orchestration and publication, not by the model. */
|
|
@@ -506,18 +609,6 @@ export const selectedPullRequestSourceLayer = (
|
|
|
506
609
|
}),
|
|
507
610
|
);
|
|
508
611
|
|
|
509
|
-
/** Profile fingerprints are SHA-256 over configuration-only signatures. */
|
|
510
|
-
export const computeProfileFingerprint = (signature: string): Effect.Effect<string> =>
|
|
511
|
-
Effect.promise(async () => {
|
|
512
|
-
const digest = await globalThis.crypto.subtle.digest(
|
|
513
|
-
"SHA-256",
|
|
514
|
-
new TextEncoder().encode(signature),
|
|
515
|
-
);
|
|
516
|
-
return Array.from(new Uint8Array(digest))
|
|
517
|
-
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
518
|
-
.join("");
|
|
519
|
-
});
|
|
520
|
-
|
|
521
612
|
/** Build the full-surface mission used only to resolve profile guidance. */
|
|
522
613
|
export const buildProfileMission = (
|
|
523
614
|
metadata: PullRequestMetadata,
|
package/src/internal/run.ts
CHANGED
|
@@ -32,9 +32,11 @@ import {
|
|
|
32
32
|
import {
|
|
33
33
|
fromStoredConcern,
|
|
34
34
|
fromStoredFinding,
|
|
35
|
+
MAX_STORED_UNREVIEWED_PASSES,
|
|
35
36
|
MAX_STORED_UNREVIEWED_PATHS,
|
|
36
37
|
ReviewExecutionContext,
|
|
37
38
|
ReviewState,
|
|
39
|
+
StoredUnreviewedPass,
|
|
38
40
|
toStoredConcern,
|
|
39
41
|
toStoredFinding,
|
|
40
42
|
} from "./review-state.ts";
|
|
@@ -171,6 +173,7 @@ interface ReviewCore {
|
|
|
171
173
|
readonly inputCoverage: ReviewInputCoverage;
|
|
172
174
|
readonly assurance: ReviewAssurance;
|
|
173
175
|
readonly unreviewedPaths: ReadonlyArray<string>;
|
|
176
|
+
readonly unreviewedPasses?: ReadonlyArray<StoredUnreviewedPass> | undefined;
|
|
174
177
|
readonly turns: number;
|
|
175
178
|
}
|
|
176
179
|
|
|
@@ -271,6 +274,7 @@ const settleReviewRun = (
|
|
|
271
274
|
unresolvedFindings: activeFindings.map(toStoredFinding),
|
|
272
275
|
unresolvedConcerns: activeConcerns.map(toStoredConcern),
|
|
273
276
|
unreviewedPaths,
|
|
277
|
+
unreviewedPasses: (core.unreviewedPasses ?? []).slice(0, MAX_STORED_UNREVIEWED_PASSES),
|
|
274
278
|
settled,
|
|
275
279
|
lastReviewMode: executionContext.mode,
|
|
276
280
|
})
|
|
@@ -449,6 +453,14 @@ export const executeFanOutReview = <Provider, ModelProvides, ModelRequires>(
|
|
|
449
453
|
totalChangedFiles: totalFiles,
|
|
450
454
|
maxFindings: options.maxFindings,
|
|
451
455
|
budget: toRunBudgetHook(budget),
|
|
456
|
+
...(executionContext !== undefined && executionContext.retryPaths.length > 0
|
|
457
|
+
? {
|
|
458
|
+
retry: {
|
|
459
|
+
paths: executionContext.retryPaths,
|
|
460
|
+
stages: executionContext.retryStages,
|
|
461
|
+
},
|
|
462
|
+
}
|
|
463
|
+
: {}),
|
|
452
464
|
});
|
|
453
465
|
const inputCoverage = fanOutInputCoverage({
|
|
454
466
|
plan: pipeline.plan,
|
|
@@ -464,6 +476,14 @@ export const executeFanOutReview = <Provider, ModelProvides, ModelRequires>(
|
|
|
464
476
|
inputCoverage,
|
|
465
477
|
assurance: pipeline.assurance,
|
|
466
478
|
unreviewedPaths: pipeline.unreviewedPaths,
|
|
479
|
+
unreviewedPasses: pipeline.unreviewedPasses
|
|
480
|
+
.slice(0, MAX_STORED_UNREVIEWED_PASSES)
|
|
481
|
+
.map((pass) =>
|
|
482
|
+
StoredUnreviewedPass.make({
|
|
483
|
+
stage: pass.stage,
|
|
484
|
+
paths: pass.paths.slice(0, 12),
|
|
485
|
+
}),
|
|
486
|
+
),
|
|
467
487
|
turns: pipeline.turns,
|
|
468
488
|
},
|
|
469
489
|
{ metadata, files, anchorFiles, fingerprint, usage },
|