@effect-agent/pr-review 0.1.0-beta.26 → 0.1.0-beta.28
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 +26 -3
- package/dist/action.d.mts +11 -8
- package/dist/action.mjs +24 -9
- package/dist/action.mjs.map +1 -1
- package/dist/cli.mjs +7 -4
- package/dist/cli.mjs.map +1 -1
- package/dist/{fan-out-CMEsbFLk.d.mts → fan-out-C3yG1cx3.d.mts} +45 -20
- package/dist/{github-NjgxGqwM.mjs → github-CCuLgyqb.mjs} +254 -81
- package/dist/github-CCuLgyqb.mjs.map +1 -0
- package/dist/index.d.mts +164 -13
- package/dist/index.mjs +3 -3
- package/dist/{providers-CODZQCmL.mjs → providers-Br9FRn7j.mjs} +35 -12
- package/dist/providers-Br9FRn7j.mjs.map +1 -0
- package/dist/testing.d.mts +3 -3
- package/dist/testing.mjs +1 -1
- package/dist/testing.mjs.map +1 -1
- package/package.json +2 -2
- package/src/action.ts +48 -11
- package/src/cli.ts +14 -2
- package/src/internal/action-entry.ts +1 -0
- package/src/internal/factory.ts +2 -1
- package/src/internal/fan-out.ts +150 -66
- package/src/internal/fixtures.ts +3 -3
- package/src/internal/github.ts +169 -22
- package/src/internal/providers.ts +47 -8
- package/src/internal/review-state.ts +85 -47
- package/src/internal/run.ts +8 -3
- package/dist/github-NjgxGqwM.mjs.map +0 -1
- package/dist/providers-CODZQCmL.mjs.map +0 -1
|
@@ -670,13 +670,13 @@ const adjudicationIdentity = (adjudication) => adjudication.path !== void 0 && a
|
|
|
670
670
|
const MAX_STORED_UNREVIEWED_PATHS = 100;
|
|
671
671
|
/** Failed-pass records stored beside the leftover paths; one per unit stage. */
|
|
672
672
|
const MAX_STORED_UNREVIEWED_PASSES = 24;
|
|
673
|
-
/** Stages a leftover path may need retried
|
|
673
|
+
/** Stages a leftover path may need retried on the next incremental run. */
|
|
674
674
|
const UnreviewedStage = Schema.Literals([
|
|
675
675
|
"discovery",
|
|
676
676
|
"specialist",
|
|
677
677
|
"verification"
|
|
678
678
|
]);
|
|
679
|
-
/** One failed fan-out pass whose
|
|
679
|
+
/** One failed fan-out pass whose stage remains attached to its exact paths. */
|
|
680
680
|
var StoredUnreviewedPass = class extends Schema.Class("@effect-agent/pr-review/StoredUnreviewedPass")({
|
|
681
681
|
stage: UnreviewedStage,
|
|
682
682
|
paths: Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12))
|
|
@@ -856,11 +856,25 @@ var ReviewHeadComparison = class extends Schema.Class("@effect-agent/pr-review/R
|
|
|
856
856
|
/** GitHub caps compare-file payloads at 300; equality is conservatively truncated. */
|
|
857
857
|
truncated: Schema.Boolean
|
|
858
858
|
}) {};
|
|
859
|
+
/**
|
|
860
|
+
* Current and previous paths for 300 PR files plus bounded stored continuity
|
|
861
|
+
* paths. The live adapter refuses a larger snapshot-comparison request.
|
|
862
|
+
*/
|
|
863
|
+
const MAX_TREE_COMPARISON_PATHS = 750;
|
|
864
|
+
/** A direct comparison of two complete commit tree snapshots. */
|
|
865
|
+
var ReviewTreeComparison = class extends Schema.Class("@effect-agent/pr-review/ReviewTreeComparison")({
|
|
866
|
+
baseSha: GitCommitSha,
|
|
867
|
+
headSha: GitCommitSha,
|
|
868
|
+
changedPaths: Schema.Array(ChangedPath).check(Schema.isMaxLength(750)),
|
|
869
|
+
/** True when GitHub returned either recursive tree incompletely. */
|
|
870
|
+
truncated: Schema.Boolean
|
|
871
|
+
}) {};
|
|
859
872
|
const fullReviewSelection = (input) => ({
|
|
860
873
|
mode: "full",
|
|
861
874
|
reason: input.reason,
|
|
862
875
|
files: input.files,
|
|
863
876
|
affectedPaths: input.files.flatMap((file) => file.previousPath === void 0 ? [file.path] : [file.path, file.previousPath]),
|
|
877
|
+
retryPasses: [],
|
|
864
878
|
retryPaths: [],
|
|
865
879
|
retryStages: [],
|
|
866
880
|
totalFiles: input.totalFiles,
|
|
@@ -886,7 +900,7 @@ const validateReviewState = (state, current, profileFingerprint) => {
|
|
|
886
900
|
const filePaths = (file) => file.previousPath === void 0 ? [file.path] : [file.path, file.previousPath];
|
|
887
901
|
const incrementalFromDelta = (input) => {
|
|
888
902
|
const currentPaths = new Set(input.fullFiles.flatMap(filePaths));
|
|
889
|
-
const affectedPaths = /* @__PURE__ */ new Set([...input.
|
|
903
|
+
const affectedPaths = /* @__PURE__ */ new Set([...input.deltaPaths, ...input.extraAffectedPaths ?? []]);
|
|
890
904
|
const initialAffectedCount = affectedPaths.size;
|
|
891
905
|
let expanded = true;
|
|
892
906
|
while (expanded) {
|
|
@@ -901,29 +915,43 @@ const incrementalFromDelta = (input) => {
|
|
|
901
915
|
}
|
|
902
916
|
}
|
|
903
917
|
const selectedByPath = /* @__PURE__ */ new Map();
|
|
904
|
-
for (const file of input.deltaFiles) if (currentPaths.has(file.path) || file.previousPath !== void 0 && currentPaths.has(file.previousPath)) selectedByPath.set(file.path, file);
|
|
905
918
|
const carriedPaths = input.priorState.unreviewedPaths.filter((path) => currentPaths.has(path));
|
|
906
919
|
const retryOnly = /* @__PURE__ */ new Set();
|
|
907
|
-
const retryStages = /* @__PURE__ */ new Set();
|
|
908
920
|
for (const path of carriedPaths) {
|
|
909
921
|
if (affectedPaths.has(path)) continue;
|
|
910
922
|
retryOnly.add(path);
|
|
911
|
-
for (const pass of input.priorState.unreviewedPasses) if (pass.paths.includes(path)) retryStages.add(pass.stage);
|
|
912
923
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
924
|
+
const retryPathsByStage = /* @__PURE__ */ new Map();
|
|
925
|
+
const representedRetryPaths = /* @__PURE__ */ new Set();
|
|
926
|
+
for (const pass of input.priorState.unreviewedPasses) for (const path of pass.paths) {
|
|
927
|
+
if (!retryOnly.has(path)) continue;
|
|
928
|
+
const paths = retryPathsByStage.get(pass.stage) ?? /* @__PURE__ */ new Set();
|
|
929
|
+
paths.add(path);
|
|
930
|
+
retryPathsByStage.set(pass.stage, paths);
|
|
931
|
+
representedRetryPaths.add(path);
|
|
916
932
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
retryStages.add("verification");
|
|
933
|
+
for (const path of retryOnly) {
|
|
934
|
+
if (representedRetryPaths.has(path)) continue;
|
|
935
|
+
retryOnly.delete(path);
|
|
936
|
+
affectedPaths.add(path);
|
|
922
937
|
}
|
|
938
|
+
const retryPasses = [
|
|
939
|
+
"discovery",
|
|
940
|
+
"specialist",
|
|
941
|
+
"verification"
|
|
942
|
+
].flatMap((stage) => {
|
|
943
|
+
const paths = [...retryPathsByStage.get(stage) ?? []].filter((path) => retryOnly.has(path)).sort();
|
|
944
|
+
return Array.from({ length: Math.ceil(paths.length / 12) }, (_, index) => StoredUnreviewedPass.make({
|
|
945
|
+
stage,
|
|
946
|
+
paths: paths.slice(index * 12, (index + 1) * 12)
|
|
947
|
+
}));
|
|
948
|
+
});
|
|
949
|
+
const retryPaths = [...retryOnly].sort();
|
|
950
|
+
const retryStages = [...new Set(retryPasses.map((pass) => pass.stage))];
|
|
923
951
|
for (const file of input.fullFiles) if (affectedPaths.has(file.path) || file.previousPath !== void 0 && affectedPaths.has(file.previousPath) || retryOnly.has(file.path) || file.previousPath !== void 0 && retryOnly.has(file.previousPath)) selectedByPath.set(file.path, file);
|
|
924
952
|
const selectedFiles = [...selectedByPath.values()].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
925
|
-
const leftoverCount =
|
|
926
|
-
const carriedReason = leftoverCount > 0 ? `; retrying ${leftoverCount} unchanged leftover path(s)
|
|
953
|
+
const leftoverCount = retryPaths.length;
|
|
954
|
+
const carriedReason = leftoverCount > 0 ? `; retrying ${leftoverCount} unchanged leftover path(s) by recorded failed stage` : carriedPaths.length > 0 ? `; retrying ${carriedPaths.length} carried unreviewed path(s)` : "";
|
|
927
955
|
const concernPathCount = affectedPaths.size - initialAffectedCount;
|
|
928
956
|
const concernReason = concernPathCount === 0 ? "" : `; reopening ${concernPathCount} related concern path(s) for context`;
|
|
929
957
|
return {
|
|
@@ -931,8 +959,9 @@ const incrementalFromDelta = (input) => {
|
|
|
931
959
|
reason: `${input.reason}${carriedReason}${concernReason}`,
|
|
932
960
|
files: selectedFiles,
|
|
933
961
|
affectedPaths: [...affectedPaths].sort(),
|
|
934
|
-
|
|
935
|
-
|
|
962
|
+
retryPasses,
|
|
963
|
+
retryPaths,
|
|
964
|
+
retryStages,
|
|
936
965
|
totalFiles: selectedFiles.length,
|
|
937
966
|
baselineSha: input.priorState.reviewedHeadSha,
|
|
938
967
|
priorState: input.priorState,
|
|
@@ -964,24 +993,27 @@ const selectReviewRange = (input) => {
|
|
|
964
993
|
baseReason = `; base advanced from ${input.priorState.baseSha.slice(0, 7)} and overlapping PR paths were included`;
|
|
965
994
|
}
|
|
966
995
|
return incrementalFromDelta({
|
|
967
|
-
current: input.current,
|
|
968
996
|
fullFiles: input.fullFiles,
|
|
969
997
|
profileFingerprint: input.profileFingerprint,
|
|
970
998
|
priorState: input.priorState,
|
|
971
|
-
|
|
999
|
+
deltaPaths: comparison.files.flatMap(filePaths),
|
|
972
1000
|
extraAffectedPaths: extraAffected,
|
|
973
1001
|
reason: `changes since reviewed head ${input.priorState.reviewedHeadSha.slice(0, 7)}${baseReason}`
|
|
974
1002
|
});
|
|
975
1003
|
}
|
|
976
1004
|
const contentComparison = input.contentComparison;
|
|
977
|
-
if (contentComparison !== void 0
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1005
|
+
if (contentComparison !== void 0) {
|
|
1006
|
+
if (contentComparison.baseSha !== input.priorState.reviewedHeadSha || contentComparison.headSha !== input.current.headSha) return full("the rewritten-head tree snapshot comparison did not match the requested heads");
|
|
1007
|
+
if (contentComparison.truncated) return full("the rewritten-head tree snapshot comparison was truncated");
|
|
1008
|
+
return incrementalFromDelta({
|
|
1009
|
+
fullFiles: input.fullFiles,
|
|
1010
|
+
profileFingerprint: input.profileFingerprint,
|
|
1011
|
+
priorState: input.priorState,
|
|
1012
|
+
deltaPaths: contentComparison.changedPaths,
|
|
1013
|
+
reason: `rewritten history; contents changed since reviewed head ${input.priorState.reviewedHeadSha.slice(0, 7)}`
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
if (input.contentComparisonFailure !== void 0) return full(`the rewritten-head tree snapshot comparison failed: ${input.contentComparisonFailure.slice(0, 2048)}`);
|
|
985
1017
|
if (comparison === void 0) return full("the incremental head comparison was unavailable");
|
|
986
1018
|
if (comparison.truncated) return full("the incremental comparison exceeded GitHub's file bound");
|
|
987
1019
|
return full("the prior reviewed head is not an ancestor of the current head");
|
|
@@ -2520,9 +2552,26 @@ const remapPlanUnitIds = (plan, offset) => {
|
|
|
2520
2552
|
});
|
|
2521
2553
|
};
|
|
2522
2554
|
const scheduleFanOutWork = (input) => {
|
|
2523
|
-
const
|
|
2524
|
-
|
|
2525
|
-
|
|
2555
|
+
const requestedRetryPasses = input.retry?.passes ?? input.retry?.stages?.map((stage) => ({
|
|
2556
|
+
stage,
|
|
2557
|
+
paths: input.retry?.paths ?? []
|
|
2558
|
+
})) ?? [];
|
|
2559
|
+
const requestedStagesByPath = /* @__PURE__ */ new Map();
|
|
2560
|
+
for (const pass of requestedRetryPasses) for (const path of pass.paths) {
|
|
2561
|
+
const stages = requestedStagesByPath.get(path) ?? /* @__PURE__ */ new Set();
|
|
2562
|
+
stages.add(pass.stage);
|
|
2563
|
+
requestedStagesByPath.set(path, stages);
|
|
2564
|
+
}
|
|
2565
|
+
const canonicalPathByKnownPath = /* @__PURE__ */ new Map();
|
|
2566
|
+
const retryStagesByPath = /* @__PURE__ */ new Map();
|
|
2567
|
+
for (const file of input.files) {
|
|
2568
|
+
canonicalPathByKnownPath.set(file.path, file.path);
|
|
2569
|
+
if (file.previousPath !== void 0) canonicalPathByKnownPath.set(file.previousPath, file.path);
|
|
2570
|
+
const requested = [requestedStagesByPath.get(file.path), ...file.previousPath === void 0 ? [] : [requestedStagesByPath.get(file.previousPath)]];
|
|
2571
|
+
const stages = new Set(requested.flatMap((entry) => [...entry ?? []]));
|
|
2572
|
+
if (stages.size > 0) retryStagesByPath.set(file.path, stages);
|
|
2573
|
+
}
|
|
2574
|
+
if (retryStagesByPath.size === 0) {
|
|
2526
2575
|
const plan = planReviewUnits(input.files, { totalChangedFiles: input.totalChangedFiles });
|
|
2527
2576
|
const passesByUnit = /* @__PURE__ */ new Map();
|
|
2528
2577
|
for (const pass of plan.discoveryPasses) {
|
|
@@ -2533,39 +2582,67 @@ const scheduleFanOutWork = (input) => {
|
|
|
2533
2582
|
return {
|
|
2534
2583
|
plan,
|
|
2535
2584
|
passesByUnit,
|
|
2536
|
-
|
|
2585
|
+
overflowRetryPasses: []
|
|
2537
2586
|
};
|
|
2538
2587
|
}
|
|
2539
|
-
const
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
const retryPlan = remapPlanUnitIds(planReviewUnits(retryFiles, { totalChangedFiles: input.totalChangedFiles }), freshPlan.units.length);
|
|
2543
|
-
const acceptedFresh = freshPlan.units.slice(0, 8);
|
|
2544
|
-
const acceptedRetry = retryPlan.units.slice(0, Math.max(0, 8 - acceptedFresh.length));
|
|
2545
|
-
const overflowRetryPaths = retryPlan.units.slice(acceptedRetry.length).flatMap((unit) => [...unit.paths]);
|
|
2546
|
-
const retryPassFilter = (pass) => {
|
|
2547
|
-
const stage = pass.perspective === "risk-specialist" ? "specialist" : "discovery";
|
|
2548
|
-
return retryStages.has(stage);
|
|
2588
|
+
const discoveryStagesFor = (stages) => {
|
|
2589
|
+
if (stages.has("verification")) return ["discovery", "specialist"];
|
|
2590
|
+
return [...stages.has("discovery") ? ["discovery"] : [], ...stages.has("specialist") ? ["specialist"] : []];
|
|
2549
2591
|
};
|
|
2550
|
-
const
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2592
|
+
const freshFiles = input.files.filter((file) => !retryStagesByPath.has(file.path));
|
|
2593
|
+
const retryGroups = /* @__PURE__ */ new Map();
|
|
2594
|
+
for (const file of input.files) {
|
|
2595
|
+
const retryStages = retryStagesByPath.get(file.path);
|
|
2596
|
+
if (retryStages === void 0) continue;
|
|
2597
|
+
const stages = discoveryStagesFor(retryStages);
|
|
2598
|
+
const key = stages.join("|");
|
|
2599
|
+
const group = retryGroups.get(key) ?? {
|
|
2600
|
+
stages,
|
|
2601
|
+
files: []
|
|
2602
|
+
};
|
|
2603
|
+
group.files.push(file);
|
|
2604
|
+
retryGroups.set(key, group);
|
|
2605
|
+
}
|
|
2606
|
+
const batches = [...freshFiles.length === 0 ? [] : [{
|
|
2607
|
+
stages: ["discovery", "specialist"],
|
|
2608
|
+
files: freshFiles
|
|
2609
|
+
}], ...[...retryGroups.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([, group]) => group)];
|
|
2610
|
+
const subplans = [];
|
|
2611
|
+
const acceptedUnits = [];
|
|
2612
|
+
const rejectedUnits = [];
|
|
2613
|
+
const discoveryPasses = [];
|
|
2614
|
+
for (const batch of batches) {
|
|
2615
|
+
const batchPlan = remapPlanUnitIds(planReviewUnits(batch.files, { totalChangedFiles: batch.files.length }), acceptedUnits.length);
|
|
2616
|
+
subplans.push(batchPlan);
|
|
2617
|
+
const accepted = batchPlan.units.slice(0, Math.max(0, 8 - acceptedUnits.length));
|
|
2618
|
+
acceptedUnits.push(...accepted);
|
|
2619
|
+
rejectedUnits.push(...batchPlan.units.slice(accepted.length));
|
|
2620
|
+
const acceptedIds = new Set(accepted.map((unit) => unit.unitId));
|
|
2621
|
+
discoveryPasses.push(...batchPlan.discoveryPasses.filter((pass) => {
|
|
2622
|
+
const stage = pass.perspective === "risk-specialist" ? "specialist" : "discovery";
|
|
2623
|
+
return acceptedIds.has(pass.unitId) && batch.stages.includes(stage);
|
|
2624
|
+
}));
|
|
2625
|
+
}
|
|
2626
|
+
const acceptedPaths = new Set(acceptedUnits.flatMap((unit) => unit.paths));
|
|
2627
|
+
const incompletePlannedPaths = /* @__PURE__ */ new Set([
|
|
2628
|
+
...subplans.flatMap((plan) => plan.partialEvidencePaths),
|
|
2629
|
+
...subplans.flatMap((plan) => plan.unassignedPaths),
|
|
2630
|
+
...rejectedUnits.flatMap((unit) => unit.paths)
|
|
2631
|
+
]);
|
|
2632
|
+
const partialEvidencePaths = [...incompletePlannedPaths].filter((path) => acceptedPaths.has(path)).sort();
|
|
2633
|
+
const unassignedPaths = [...incompletePlannedPaths].filter((path) => !acceptedPaths.has(path)).sort();
|
|
2634
|
+
const rejectedEvidenceShards = rejectedUnits.flatMap((unit) => unit.evidenceShards);
|
|
2635
|
+
const undiffablePaths = [...new Set(subplans.flatMap((plan) => plan.undiffablePaths))].sort();
|
|
2555
2636
|
const plan = ReviewUnitPlan.make({
|
|
2556
2637
|
totalFiles: input.files.length,
|
|
2557
|
-
truncated:
|
|
2558
|
-
units:
|
|
2638
|
+
truncated: input.files.length < input.totalChangedFiles,
|
|
2639
|
+
units: acceptedUnits,
|
|
2559
2640
|
discoveryPasses,
|
|
2560
|
-
undiffablePaths
|
|
2561
|
-
partialEvidencePaths
|
|
2562
|
-
unassignedEvidenceShardCount:
|
|
2563
|
-
unassignedEvidenceShardIds: [...
|
|
2564
|
-
unassignedPaths
|
|
2565
|
-
...freshPlan.unassignedPaths,
|
|
2566
|
-
...retryPlan.unassignedPaths,
|
|
2567
|
-
...overflowRetryPaths
|
|
2568
|
-
])].sort()
|
|
2641
|
+
undiffablePaths,
|
|
2642
|
+
partialEvidencePaths,
|
|
2643
|
+
unassignedEvidenceShardCount: subplans.reduce((total, item) => total + item.unassignedEvidenceShardCount, 0) + rejectedEvidenceShards.length,
|
|
2644
|
+
unassignedEvidenceShardIds: [...subplans.flatMap((item) => item.unassignedEvidenceShardIds), ...rejectedEvidenceShards.map((shard) => shard.shardId)].slice(0, 96),
|
|
2645
|
+
unassignedPaths
|
|
2569
2646
|
});
|
|
2570
2647
|
const passesByUnit = /* @__PURE__ */ new Map();
|
|
2571
2648
|
for (const pass of discoveryPasses) {
|
|
@@ -2573,10 +2650,33 @@ const scheduleFanOutWork = (input) => {
|
|
|
2573
2650
|
passes.push(pass);
|
|
2574
2651
|
passesByUnit.set(pass.unitId, passes);
|
|
2575
2652
|
}
|
|
2653
|
+
const incompletePaths = /* @__PURE__ */ new Set([
|
|
2654
|
+
...partialEvidencePaths,
|
|
2655
|
+
...unassignedPaths,
|
|
2656
|
+
...undiffablePaths
|
|
2657
|
+
]);
|
|
2658
|
+
const overflowRetryPathsByStage = /* @__PURE__ */ new Map();
|
|
2659
|
+
for (const pass of requestedRetryPasses) for (const path of pass.paths) {
|
|
2660
|
+
const canonicalPath = canonicalPathByKnownPath.get(path);
|
|
2661
|
+
if (canonicalPath === void 0 || !incompletePaths.has(canonicalPath)) continue;
|
|
2662
|
+
const paths = overflowRetryPathsByStage.get(pass.stage) ?? /* @__PURE__ */ new Set();
|
|
2663
|
+
paths.add(canonicalPath);
|
|
2664
|
+
overflowRetryPathsByStage.set(pass.stage, paths);
|
|
2665
|
+
}
|
|
2576
2666
|
return {
|
|
2577
2667
|
plan,
|
|
2578
2668
|
passesByUnit,
|
|
2579
|
-
|
|
2669
|
+
overflowRetryPasses: [
|
|
2670
|
+
"discovery",
|
|
2671
|
+
"specialist",
|
|
2672
|
+
"verification"
|
|
2673
|
+
].flatMap((stage) => {
|
|
2674
|
+
const paths = [...overflowRetryPathsByStage.get(stage) ?? []].sort();
|
|
2675
|
+
return Array.from({ length: Math.ceil(paths.length / 12) }, (_, index) => ({
|
|
2676
|
+
stage,
|
|
2677
|
+
paths: paths.slice(index * 12, (index + 1) * 12)
|
|
2678
|
+
}));
|
|
2679
|
+
})
|
|
2580
2680
|
};
|
|
2581
2681
|
};
|
|
2582
2682
|
/**
|
|
@@ -2586,7 +2686,7 @@ const scheduleFanOutWork = (input) => {
|
|
|
2586
2686
|
* only. The verdict is derived from confirmed severities, never model prose.
|
|
2587
2687
|
*/
|
|
2588
2688
|
const runFanOutReview = (binding, input) => Effect.gen(function* () {
|
|
2589
|
-
const { plan, passesByUnit,
|
|
2689
|
+
const { plan, passesByUnit, overflowRetryPasses } = scheduleFanOutWork(input);
|
|
2590
2690
|
const outcomes = yield* Effect.forEach(plan.units, (unit) => reviewUnit(binding, unit, passesByUnit.get(unit.unitId) ?? [], input), { concurrency: 4 });
|
|
2591
2691
|
const failedPasses = outcomes.flatMap((outcome) => outcome.failedPasses);
|
|
2592
2692
|
const unsettledCandidates = outcomes.reduce((total, outcome) => total + outcome.unsettledCandidates, 0);
|
|
@@ -2634,14 +2734,7 @@ const runFanOutReview = (binding, input) => Effect.gen(function* () {
|
|
|
2634
2734
|
...plan.partialEvidencePaths,
|
|
2635
2735
|
...plan.undiffablePaths
|
|
2636
2736
|
])].sort(),
|
|
2637
|
-
unreviewedPasses: [...outcomes.flatMap((outcome) => outcome.unreviewedPasses), ...
|
|
2638
|
-
"discovery",
|
|
2639
|
-
"specialist",
|
|
2640
|
-
"verification"
|
|
2641
|
-
]).map((stage) => ({
|
|
2642
|
-
stage,
|
|
2643
|
-
paths: overflowRetryPaths
|
|
2644
|
-
}))],
|
|
2737
|
+
unreviewedPasses: [...outcomes.flatMap((outcome) => outcome.unreviewedPasses), ...overflowRetryPasses],
|
|
2645
2738
|
turns: outcomes.reduce((total, outcome) => total + outcome.turns, 0)
|
|
2646
2739
|
};
|
|
2647
2740
|
});
|
|
@@ -3183,6 +3276,41 @@ const GitHubCompareWire = Schema.Struct({
|
|
|
3183
3276
|
merge_base_commit: Schema.Struct({ sha: Schema.String }),
|
|
3184
3277
|
files: GitHubFilesPageWire
|
|
3185
3278
|
});
|
|
3279
|
+
const GitHubGitCommitWire = Schema.Struct({
|
|
3280
|
+
sha: GitCommitSha,
|
|
3281
|
+
tree: Schema.Struct({ sha: GitCommitSha })
|
|
3282
|
+
});
|
|
3283
|
+
const GitHubTreeEntryFields = {
|
|
3284
|
+
path: Schema.String.check(Schema.isMaxLength(4096)),
|
|
3285
|
+
sha: GitCommitSha
|
|
3286
|
+
};
|
|
3287
|
+
const GitHubTreeEntryWire = Schema.Union([
|
|
3288
|
+
Schema.Struct({
|
|
3289
|
+
...GitHubTreeEntryFields,
|
|
3290
|
+
mode: Schema.Literals([
|
|
3291
|
+
"100644",
|
|
3292
|
+
"100755",
|
|
3293
|
+
"120000"
|
|
3294
|
+
]),
|
|
3295
|
+
type: Schema.Literal("blob")
|
|
3296
|
+
}),
|
|
3297
|
+
Schema.Struct({
|
|
3298
|
+
...GitHubTreeEntryFields,
|
|
3299
|
+
mode: Schema.Literal("040000"),
|
|
3300
|
+
type: Schema.Literal("tree")
|
|
3301
|
+
}),
|
|
3302
|
+
Schema.Struct({
|
|
3303
|
+
...GitHubTreeEntryFields,
|
|
3304
|
+
mode: Schema.Literal("160000"),
|
|
3305
|
+
type: Schema.Literal("commit")
|
|
3306
|
+
})
|
|
3307
|
+
]);
|
|
3308
|
+
const GitHubTreeWire = Schema.Struct({
|
|
3309
|
+
sha: GitCommitSha,
|
|
3310
|
+
tree: Schema.Array(GitHubTreeEntryWire).check(Schema.isMaxLength(1e5)),
|
|
3311
|
+
truncated: Schema.Boolean
|
|
3312
|
+
});
|
|
3313
|
+
const TreeComparisonPaths = Schema.Array(ChangedPath).check(Schema.isMaxLength(750));
|
|
3186
3314
|
/** Reviews are paged chronologically; scanning stays bounded. */
|
|
3187
3315
|
const MAX_PRIOR_REVIEW_PAGES = 5;
|
|
3188
3316
|
/** GitHub-backed PriorReviews over the pull-request reviews endpoint. */
|
|
@@ -3193,6 +3321,11 @@ const gitHubPriorReviewsLayer = Layer.effect(PriorReviews)(Effect.gen(function*
|
|
|
3193
3321
|
const reviewAuthorLogin = target.reviewAuthorLogin ?? "github-actions[bot]";
|
|
3194
3322
|
const decodePage = Schema.decodeUnknownEffect(GitHubPriorReviewsPageWire);
|
|
3195
3323
|
const asLookupFailure = (error) => PriorReviewLookupFailure.make({ reason: `${error._tag}: ${error.message ?? "request failed"}`.slice(0, 2048) });
|
|
3324
|
+
const asTreeLookupFailure = (operation) => (error) => PriorReviewLookupFailure.make({ reason: `${operation}: ${error._tag}: ${error.message ?? "request failed"}`.slice(0, 2048) });
|
|
3325
|
+
const decodeLookupJson = (schema, operation) => {
|
|
3326
|
+
const decode = Schema.decodeUnknownEffect(schema);
|
|
3327
|
+
return (response) => response.json.pipe(Effect.mapError(asTreeLookupFailure(operation)), Effect.flatMap((body) => decode(body).pipe(Effect.mapError(asTreeLookupFailure(operation)))));
|
|
3328
|
+
};
|
|
3196
3329
|
const readMarkers = (authenticator) => Effect.gen(function* () {
|
|
3197
3330
|
const perPage = 100;
|
|
3198
3331
|
let latest = Option.none();
|
|
@@ -3219,8 +3352,8 @@ const gitHubPriorReviewsLayer = Layer.effect(PriorReviews)(Effect.gen(function*
|
|
|
3219
3352
|
latestState
|
|
3220
3353
|
};
|
|
3221
3354
|
}).pipe(Effect.provideService(HttpClient.HttpClient, client));
|
|
3222
|
-
const compareCommits = (baseSha, headSha
|
|
3223
|
-
const wire = yield* (yield* HttpClient.execute(withCommonHeaders(HttpClientRequest.get(`${target.apiUrl}/repos/${target.repository}/compare/${encodeURIComponent(baseSha)}
|
|
3355
|
+
const compareCommits = (baseSha, headSha) => Effect.gen(function* () {
|
|
3356
|
+
const wire = yield* (yield* HttpClient.execute(withCommonHeaders(HttpClientRequest.get(`${target.apiUrl}/repos/${target.repository}/compare/${encodeURIComponent(baseSha)}...${encodeURIComponent(headSha)}`).pipe(HttpClientRequest.acceptJson), target.token)).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.mapError(asLookupFailure))).json.pipe(Effect.mapError(asLookupFailure), Effect.flatMap((body) => Schema.decodeUnknownEffect(GitHubCompareWire)(body).pipe(Effect.mapError(asLookupFailure))));
|
|
3224
3357
|
const files = wire.files.map(toChangedFile);
|
|
3225
3358
|
return ReviewHeadComparison.make({
|
|
3226
3359
|
status: wire.status,
|
|
@@ -3231,21 +3364,61 @@ const gitHubPriorReviewsLayer = Layer.effect(PriorReviews)(Effect.gen(function*
|
|
|
3231
3364
|
truncated: files.length >= 300
|
|
3232
3365
|
});
|
|
3233
3366
|
}).pipe(Effect.provideService(HttpClient.HttpClient, client));
|
|
3234
|
-
const
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3367
|
+
const readTreeSnapshot = Effect.fn("PriorReviews.readTreeSnapshot")(function* (commitSha) {
|
|
3368
|
+
const commitResponse = yield* client.execute(withCommonHeaders(HttpClientRequest.get(`${target.apiUrl}/repos/${target.repository}/git/commits/${encodeURIComponent(commitSha)}`).pipe(HttpClientRequest.acceptJson), target.token)).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.mapError(asTreeLookupFailure("get Git commit")));
|
|
3369
|
+
const commit = yield* decodeLookupJson(GitHubGitCommitWire, "decode Git commit")(commitResponse);
|
|
3370
|
+
if (commit.sha !== commitSha) return yield* PriorReviewLookupFailure.make({ reason: `GitHub returned commit ${commit.sha} for requested snapshot ${commitSha}` });
|
|
3371
|
+
const treeResponse = yield* client.execute(withCommonHeaders(HttpClientRequest.get(`${target.apiUrl}/repos/${target.repository}/git/trees/${encodeURIComponent(commit.tree.sha)}`).pipe(HttpClientRequest.acceptJson, HttpClientRequest.setUrlParams({ recursive: "1" })), target.token)).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.mapError(asTreeLookupFailure("get recursive Git tree")));
|
|
3372
|
+
const tree = yield* decodeLookupJson(GitHubTreeWire, "decode recursive Git tree")(treeResponse);
|
|
3373
|
+
if (tree.sha !== commit.tree.sha) return yield* PriorReviewLookupFailure.make({ reason: `GitHub returned tree ${tree.sha} for requested tree ${commit.tree.sha}` });
|
|
3374
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3375
|
+
for (const entry of tree.tree) {
|
|
3376
|
+
if (entries.has(entry.path)) return yield* PriorReviewLookupFailure.make({ reason: `GitHub returned duplicate path '${entry.path}' in tree ${tree.sha}` });
|
|
3377
|
+
entries.set(entry.path, entry);
|
|
3378
|
+
}
|
|
3379
|
+
return {
|
|
3380
|
+
entries,
|
|
3381
|
+
truncated: tree.truncated
|
|
3382
|
+
};
|
|
3383
|
+
});
|
|
3384
|
+
const compareTrees = Effect.fn("PriorReviews.compareTrees")(function* (baseSha, headSha, paths) {
|
|
3385
|
+
const decodeSha = Schema.decodeUnknownEffect(GitCommitSha);
|
|
3386
|
+
const [validatedBaseSha, validatedHeadSha, validatedPaths] = yield* Effect.all([
|
|
3387
|
+
decodeSha(baseSha),
|
|
3388
|
+
decodeSha(headSha),
|
|
3389
|
+
Schema.decodeUnknownEffect(TreeComparisonPaths)(paths)
|
|
3390
|
+
]).pipe(Effect.mapError(asTreeLookupFailure("validate tree comparison request")));
|
|
3391
|
+
const uniquePaths = [...new Set(validatedPaths)].sort();
|
|
3392
|
+
const { base, head } = yield* Effect.all({
|
|
3393
|
+
base: readTreeSnapshot(validatedBaseSha),
|
|
3394
|
+
head: readTreeSnapshot(validatedHeadSha)
|
|
3395
|
+
}, { concurrency: 2 });
|
|
3396
|
+
if (base.truncated || head.truncated) return ReviewTreeComparison.make({
|
|
3397
|
+
baseSha: validatedBaseSha,
|
|
3398
|
+
headSha: validatedHeadSha,
|
|
3399
|
+
changedPaths: [],
|
|
3400
|
+
truncated: true
|
|
3401
|
+
});
|
|
3402
|
+
const changedPaths = uniquePaths.filter((path) => {
|
|
3403
|
+
const before = base.entries.get(path);
|
|
3404
|
+
const after = head.entries.get(path);
|
|
3405
|
+
if (before === void 0 || after === void 0) return before !== after;
|
|
3406
|
+
return before.sha !== after.sha || before.mode !== after.mode || before.type !== after.type;
|
|
3407
|
+
});
|
|
3408
|
+
return ReviewTreeComparison.make({
|
|
3409
|
+
baseSha: validatedBaseSha,
|
|
3410
|
+
headSha: validatedHeadSha,
|
|
3411
|
+
changedPaths,
|
|
3412
|
+
truncated: false
|
|
3413
|
+
});
|
|
3414
|
+
});
|
|
3242
3415
|
return PriorReviews.of({
|
|
3243
3416
|
latestFingerprint: readMarkers(Option.none()).pipe(Effect.map((markers) => markers.latestFingerprint)),
|
|
3244
3417
|
latestState: Effect.gen(function* () {
|
|
3245
3418
|
const authenticator = yield* ReviewStateAuthenticator;
|
|
3246
3419
|
return yield* readMarkers(Option.some(authenticator)).pipe(Effect.map((markers) => markers.latestState));
|
|
3247
3420
|
}),
|
|
3248
|
-
compareHeads:
|
|
3421
|
+
compareHeads: compareCommits,
|
|
3249
3422
|
compareTrees
|
|
3250
3423
|
});
|
|
3251
3424
|
}));
|
|
@@ -3259,6 +3432,6 @@ const fingerprintUnchanged = (current) => Effect.gen(function* () {
|
|
|
3259
3432
|
return Option.isSome(latest) && latest.value === current;
|
|
3260
3433
|
});
|
|
3261
3434
|
//#endregion
|
|
3262
|
-
export { ReviewDiscoveryPass as $,
|
|
3435
|
+
export { ReviewDiscoveryPass as $, ReadFileDiff as $n, MAX_STORED_ADJUDICATIONS as $t, MAX_CHILD_FINDINGS as A, toStoredFinding as An, hasReviewableContent as Ar, buildPriorReviewContext as At, assessmentSettlesSuggestionExactly as B, FileSliceQuery as Bn, threadFindingTarget as Bt, FileReviewBrief as C, fromStoredFinding as Cn, normalizeRepoRelativePath as Cr, anchorViolation as Ct, FileReviewer as D, selectReviewRange as Dn, MAX_REVIEW_CONTENT_CHARS as Dr, MAX_THREAD_ADJUDICATION_COMMANDS as Dt, FileReviewToolkit as E, isLineageAncestor as En, ChangedPath as Er, AdjudicationComment as Et, ReviewCandidate as F, ChangedFilesView as Fn, noReviewAdjudicationHostLayer as Ft, makeFileReviewerInstructions as G, MAX_CONCERNS as Gn, ReviewRetirementHost as Gt, defaultFileReviewerPolicy as H, FindingSeverity as Hn, RetirableReview as Ht, ReviewCandidateId as I, CodeReview as In, parseIssueAdjudication as It, MAX_MERGED_FINDINGS as J, MAX_WALKTHROUGH_ENTRIES as Jn, hasReviewMetadataMarker as Jt, reviewCandidateSubjectKey as K, MAX_FINDINGS as Kn, ReviewRetirementReport as Kt, ReviewPassMisbehaved as L, FileDiffQuery as Ln, parseThreadAdjudication as Lt, MAX_REVIEW_CHILDREN as M, validateReviewState as Mn, parsePatch as Mr, deriveAdjudications as Mt, MAX_UNIT_CANDIDATES as N, webCryptoReviewStateAuthenticatorLayer as Nn, renderReviewContent as Nr, mergeAdjudications as Nt, FindingCandidate as O, selectedPullRequestSourceLayer as On, annotatePatch as Or, ReviewAdjudicationFailure as Ot, REVIEW_UNIT_CONCURRENCY as P, ChangedFileSummary as Pn, noReviewAdjudicationHost as Pt, MAX_UNIT_FILES as Q, ReadFile as Qn, MAX_REVIEW_STATE_MARKER_CHARS as Qt, ReviewWorkPerspective as R, FileDiffView as Rn, renderAdjudicationContextLine as Rt, DiscoveredConcern as S, fromStoredConcern as Sn, ReviewInputViolation as Sr, splitCarriedScope as St, FileReviewReport as T, fullReviewSelection as Tn, ChangedFileStatus as Tr, AdjudicableThread as Tt, fileReviewerInstructions as U, ListChangedFiles as Un, RetirableReviewComment as Ut, confirmedFindingForPublication as V, FindingCategory as Vn, INLINE_FINDING_TITLE_PATTERN as Vt, makeFileReviewerDefinition as W, ListChangedFilesQuery as Wn, ReviewRetirementFailure as Wt, MAX_REVIEW_UNITS as X, PullRequestReviewer as Xn, AdjudicationDisposition as Xt, MAX_REPORTED_UNASSIGNED_EVIDENCE_SHARDS as Y, MAX_WALKTHROUGH_SUMMARY_CHARS as Yn, retireStaleReviews as Yt, MAX_UNIT_EVIDENCE_SHARDS as Z, REVIEW_TOOL_RESULT_MAX_BYTES as Zn, GitCommitSha as Zt, computeProfileFingerprint as _, UnreviewedStage as _n, MAX_CHANGED_FILES as _r, ReviewInputCoverage as _t, PriorReviews as a, ReviewMode as an, ReviewVerdict as ar, ReviewUnit as at, CandidateAssessment as b, concernIdentity as bn, PullRequestSource as br, fanOutInputCoverage as bt, fingerprintUnchanged as c, ReviewStateAuthenticationFailure as cn, defaultReviewPolicy as cr, UNIT_EVIDENCE_CHAR_BUDGET as ct, gitHubReviewAdjudicationHostLayer as d, ReviewStateMarkerTooLarge as dn, listChangedFilesHandler as dr, planReviewUnits as dt, MAX_STORED_UNREVIEWED_PASSES as en, ReviewConcern as er, ReviewDiscoveryPerspective as et, gitHubReviewPublisherLayer as f, ReviewTreeComparison as fn, makeReviewInstructions as fr, rankAndDedupeConcerns as ft, computeChangesetFingerprint as g, StoredUnreviewedPass as gn, reviewInstructions as gr, ReviewAssurance as gt, FINGERPRINT_MARKER_LENGTH as h, StoredReviewFinding as hn, resolveGuidance as hr, FailedReviewPass as ht, PriorReviewLookupFailure as i, ReviewHeadComparison as in, ReviewToolkitLayer as ir, ReviewRiskCategory as it, MAX_FILE_REVIEW_TOOL_CALLS as j, unavailableReviewStateAuthenticatorLayer as jn, isReviewableFile as jr, collectReviewAdjudications as jt, MAX_CHILD_CONCERNS as k, toStoredConcern as kn, commentableLines as kr, ReviewAdjudicationHost as kt, gitHubPriorReviewsLayer as l, ReviewStateAuthenticator as ln, fileDiffView as lr, classifyReviewRisks as lt, parseGitHubSubmittedAt as m, StoredReviewConcern as mn, readFileHandler as mr, reviewConcernKey as mt, GitHubApiFailure as n, MAX_TREE_COMPARISON_PATHS as nn, ReviewMission as nr, ReviewEvidenceShardId as nt, PublishedReview as o, ReviewScopeMode as on, WalkthroughEntry as or, ReviewUnitId as ot, gitHubReviewRetirementHostLayer as p, StoredAdjudication as pn, readFileDiffHandler as pr, rankAndDedupeFindings as pt, runFanOutReview as q, MAX_PATCH_CHARS as qn, decideReviewRetirement as qt, GitHubReviewTarget as r, ReviewExecutionContext as rn, ReviewToolkit as rr, ReviewPassId as rt, ReviewPublisher as s, ReviewState as sn, clampMaxFindings as sr, ReviewUnitPlan as st, DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN as t, MAX_STORED_UNREVIEWED_PATHS as tn, ReviewFinding as tr, ReviewEvidenceShard as tt, gitHubPullRequestSourceLayer as u, ReviewStateMarker as un, fileReviewEvidenceChunks as ur, findingAnchorInUnitEvidence as ut, extractFingerprint as v, adjudicationIdentity as vn, MAX_FILE_CHARS as vr, assessFlatReview as vt, FileReviewEvidence as w, fullReviewExecutionContextLayer as wn, ChangedFile as wr, AUTHORIZED_ADJUDICATION_ASSOCIATIONS as wt, ConcernCandidate as x, findingIdentity as xn, PullRequestSourceFailure as xr, flatAssurance as xt, renderFingerprintMarker as y, buildProfileMission as yn, PullRequestMetadata as yr, boundedListReason as yt, ReviewWorkPhase as z, FileSlice as zn, renderPriorFindingContextLine as zt };
|
|
3263
3436
|
|
|
3264
|
-
//# sourceMappingURL=github-
|
|
3437
|
+
//# sourceMappingURL=github-CCuLgyqb.mjs.map
|