@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
package/src/internal/fan-out.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
WalkthroughEntry,
|
|
26
26
|
} from "./review-agent.ts";
|
|
27
27
|
import {
|
|
28
|
+
MAX_REPORTED_UNASSIGNED_EVIDENCE_SHARDS,
|
|
28
29
|
MAX_REVIEW_UNITS,
|
|
29
30
|
MAX_UNIT_EVIDENCE_SHARDS,
|
|
30
31
|
MAX_UNIT_FILES,
|
|
@@ -32,13 +33,13 @@ import {
|
|
|
32
33
|
planReviewUnits,
|
|
33
34
|
rankAndDedupeConcerns,
|
|
34
35
|
rankAndDedupeFindings,
|
|
36
|
+
ReviewDiscoveryPass,
|
|
35
37
|
ReviewEvidenceShardId,
|
|
36
38
|
ReviewPassId,
|
|
37
39
|
ReviewRiskCategory,
|
|
40
|
+
ReviewUnit,
|
|
38
41
|
ReviewUnitId,
|
|
39
|
-
|
|
40
|
-
type ReviewUnit,
|
|
41
|
-
type ReviewUnitPlan,
|
|
42
|
+
ReviewUnitPlan,
|
|
42
43
|
} from "./review-units.ts";
|
|
43
44
|
|
|
44
45
|
// ---------------------------------------------------------------------------
|
|
@@ -361,6 +362,11 @@ export interface FanOutPipelineOutcome {
|
|
|
361
362
|
readonly plan: ReviewUnitPlan;
|
|
362
363
|
/** Paths of units with an unsettled pass — retryable scope for the next run. */
|
|
363
364
|
readonly unreviewedPaths: ReadonlyArray<string>;
|
|
365
|
+
/** Failed stages paired with the leftover paths they still own. */
|
|
366
|
+
readonly unreviewedPasses: ReadonlyArray<{
|
|
367
|
+
readonly stage: FailedReviewPass["stage"];
|
|
368
|
+
readonly paths: ReadonlyArray<string>;
|
|
369
|
+
}>;
|
|
364
370
|
/** Total settled child turns across every scheduled pass. */
|
|
365
371
|
readonly turns: number;
|
|
366
372
|
}
|
|
@@ -372,6 +378,16 @@ export interface FanOutPipelineInput {
|
|
|
372
378
|
readonly maxFindings?: number | undefined;
|
|
373
379
|
/** Shared run budget observed by every child pass. */
|
|
374
380
|
readonly budget?: RunBudgetHook<BudgetExceeded | BudgetAdapterError> | undefined;
|
|
381
|
+
/**
|
|
382
|
+
* Unchanged leftover paths from a prior failed pass. Those units retry only
|
|
383
|
+
* the recorded stages — no second general discovery on files nobody touched.
|
|
384
|
+
*/
|
|
385
|
+
readonly retry?:
|
|
386
|
+
| {
|
|
387
|
+
readonly paths: ReadonlyArray<string>;
|
|
388
|
+
readonly stages: ReadonlyArray<FailedReviewPass["stage"]>;
|
|
389
|
+
}
|
|
390
|
+
| undefined;
|
|
375
391
|
}
|
|
376
392
|
|
|
377
393
|
const candidateOrdinal = (index: number): string => String(index + 1).padStart(3, "0");
|
|
@@ -587,6 +603,10 @@ interface UnitReviewOutcome {
|
|
|
587
603
|
readonly requiredVerificationPasses: number;
|
|
588
604
|
readonly completedVerificationPasses: number;
|
|
589
605
|
readonly unreviewedPaths: ReadonlyArray<string>;
|
|
606
|
+
readonly unreviewedPasses: ReadonlyArray<{
|
|
607
|
+
readonly stage: FailedReviewPass["stage"];
|
|
608
|
+
readonly paths: ReadonlyArray<string>;
|
|
609
|
+
}>;
|
|
590
610
|
}
|
|
591
611
|
|
|
592
612
|
const reviewUnit = <Provider, ModelProvides, ModelRequires>(
|
|
@@ -697,6 +717,10 @@ const reviewUnit = <Provider, ModelProvides, ModelRequires>(
|
|
|
697
717
|
requiredVerificationPasses,
|
|
698
718
|
completedVerificationPasses,
|
|
699
719
|
unreviewedPaths: failedPasses.length > 0 ? unit.paths : [],
|
|
720
|
+
unreviewedPasses: failedPasses.map((pass) => ({
|
|
721
|
+
stage: pass.stage,
|
|
722
|
+
paths: unit.paths,
|
|
723
|
+
})),
|
|
700
724
|
} satisfies UnitReviewOutcome;
|
|
701
725
|
});
|
|
702
726
|
|
|
@@ -737,6 +761,121 @@ const composeSummary = (plan: ReviewUnitPlan, assurance: ReviewAssurance): strin
|
|
|
737
761
|
return parts.join(" ").slice(0, 4_000);
|
|
738
762
|
};
|
|
739
763
|
|
|
764
|
+
const remapPlanUnitIds = (plan: ReviewUnitPlan, offset: number): ReviewUnitPlan => {
|
|
765
|
+
if (offset === 0) return plan;
|
|
766
|
+
const units = plan.units.map((unit, index) =>
|
|
767
|
+
ReviewUnit.make({
|
|
768
|
+
...unit,
|
|
769
|
+
unitId: `unit-${String(offset + index + 1).padStart(3, "0")}`,
|
|
770
|
+
}),
|
|
771
|
+
);
|
|
772
|
+
const mappedIds = new Map<string, string>();
|
|
773
|
+
for (const [index, unit] of plan.units.entries()) {
|
|
774
|
+
const remapped = units[index];
|
|
775
|
+
if (remapped !== undefined) {
|
|
776
|
+
mappedIds.set(unit.unitId, remapped.unitId);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return ReviewUnitPlan.make({
|
|
780
|
+
...plan,
|
|
781
|
+
units,
|
|
782
|
+
discoveryPasses: plan.discoveryPasses.map((pass) => {
|
|
783
|
+
const unitId = mappedIds.get(pass.unitId) ?? pass.unitId;
|
|
784
|
+
return ReviewDiscoveryPass.make({
|
|
785
|
+
...pass,
|
|
786
|
+
unitId,
|
|
787
|
+
passId: `${unitId}${pass.passId.slice(pass.unitId.length)}`,
|
|
788
|
+
});
|
|
789
|
+
}),
|
|
790
|
+
});
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
const scheduleFanOutWork = (
|
|
794
|
+
input: FanOutPipelineInput,
|
|
795
|
+
): {
|
|
796
|
+
readonly plan: ReviewUnitPlan;
|
|
797
|
+
readonly passesByUnit: Map<string, ReadonlyArray<ReviewDiscoveryPass>>;
|
|
798
|
+
readonly overflowRetryPaths: ReadonlyArray<string>;
|
|
799
|
+
} => {
|
|
800
|
+
const retryPathSet = new Set(input.retry?.paths ?? []);
|
|
801
|
+
const retryStages = new Set(input.retry?.stages ?? []);
|
|
802
|
+
if (retryPathSet.size === 0) {
|
|
803
|
+
const plan = planReviewUnits(input.files, { totalChangedFiles: input.totalChangedFiles });
|
|
804
|
+
const passesByUnit = new Map<string, Array<ReviewDiscoveryPass>>();
|
|
805
|
+
for (const pass of plan.discoveryPasses) {
|
|
806
|
+
const passes = passesByUnit.get(pass.unitId) ?? [];
|
|
807
|
+
passes.push(pass);
|
|
808
|
+
passesByUnit.set(pass.unitId, passes);
|
|
809
|
+
}
|
|
810
|
+
return { plan, passesByUnit, overflowRetryPaths: [] };
|
|
811
|
+
}
|
|
812
|
+
const freshFiles = input.files.filter((file) => !retryPathSet.has(file.path));
|
|
813
|
+
const retryFiles = input.files.filter((file) => retryPathSet.has(file.path));
|
|
814
|
+
const freshPlan = planReviewUnits(freshFiles, { totalChangedFiles: input.totalChangedFiles });
|
|
815
|
+
const retryPlan = remapPlanUnitIds(
|
|
816
|
+
planReviewUnits(retryFiles, { totalChangedFiles: input.totalChangedFiles }),
|
|
817
|
+
freshPlan.units.length,
|
|
818
|
+
);
|
|
819
|
+
const acceptedFresh = freshPlan.units.slice(0, MAX_REVIEW_UNITS);
|
|
820
|
+
const acceptedRetry = retryPlan.units.slice(
|
|
821
|
+
0,
|
|
822
|
+
Math.max(0, MAX_REVIEW_UNITS - acceptedFresh.length),
|
|
823
|
+
);
|
|
824
|
+
const overflowRetryPaths = retryPlan.units
|
|
825
|
+
.slice(acceptedRetry.length)
|
|
826
|
+
.flatMap((unit) => [...unit.paths]);
|
|
827
|
+
const retryPassFilter = (pass: ReviewDiscoveryPass): boolean => {
|
|
828
|
+
const stage = pass.perspective === "risk-specialist" ? "specialist" : "discovery";
|
|
829
|
+
return retryStages.has(stage);
|
|
830
|
+
};
|
|
831
|
+
const acceptedRetryIds = new Set(acceptedRetry.map((unit) => unit.unitId));
|
|
832
|
+
let retryPasses = retryPlan.discoveryPasses.filter(
|
|
833
|
+
(pass) => acceptedRetryIds.has(pass.unitId) && retryPassFilter(pass),
|
|
834
|
+
);
|
|
835
|
+
if (
|
|
836
|
+
retryStages.has("verification") &&
|
|
837
|
+
!retryStages.has("discovery") &&
|
|
838
|
+
!retryStages.has("specialist")
|
|
839
|
+
) {
|
|
840
|
+
retryPasses = retryPlan.discoveryPasses.filter((pass) => acceptedRetryIds.has(pass.unitId));
|
|
841
|
+
}
|
|
842
|
+
const acceptedFreshIds = new Set(acceptedFresh.map((unit) => unit.unitId));
|
|
843
|
+
const freshPasses = freshPlan.discoveryPasses.filter((pass) => acceptedFreshIds.has(pass.unitId));
|
|
844
|
+
const discoveryPasses = [...freshPasses, ...retryPasses];
|
|
845
|
+
const plan = ReviewUnitPlan.make({
|
|
846
|
+
totalFiles: input.files.length,
|
|
847
|
+
truncated: freshPlan.truncated || retryPlan.truncated,
|
|
848
|
+
units: [...acceptedFresh, ...acceptedRetry],
|
|
849
|
+
discoveryPasses,
|
|
850
|
+
undiffablePaths: [
|
|
851
|
+
...new Set([...freshPlan.undiffablePaths, ...retryPlan.undiffablePaths]),
|
|
852
|
+
].sort(),
|
|
853
|
+
partialEvidencePaths: [
|
|
854
|
+
...new Set([...freshPlan.partialEvidencePaths, ...retryPlan.partialEvidencePaths]),
|
|
855
|
+
].sort(),
|
|
856
|
+
unassignedEvidenceShardCount:
|
|
857
|
+
freshPlan.unassignedEvidenceShardCount + retryPlan.unassignedEvidenceShardCount,
|
|
858
|
+
unassignedEvidenceShardIds: [
|
|
859
|
+
...freshPlan.unassignedEvidenceShardIds,
|
|
860
|
+
...retryPlan.unassignedEvidenceShardIds,
|
|
861
|
+
].slice(0, MAX_REPORTED_UNASSIGNED_EVIDENCE_SHARDS),
|
|
862
|
+
unassignedPaths: [
|
|
863
|
+
...new Set([
|
|
864
|
+
...freshPlan.unassignedPaths,
|
|
865
|
+
...retryPlan.unassignedPaths,
|
|
866
|
+
...overflowRetryPaths,
|
|
867
|
+
]),
|
|
868
|
+
].sort(),
|
|
869
|
+
});
|
|
870
|
+
const passesByUnit = new Map<string, Array<ReviewDiscoveryPass>>();
|
|
871
|
+
for (const pass of discoveryPasses) {
|
|
872
|
+
const passes = passesByUnit.get(pass.unitId) ?? [];
|
|
873
|
+
passes.push(pass);
|
|
874
|
+
passesByUnit.set(pass.unitId, passes);
|
|
875
|
+
}
|
|
876
|
+
return { plan, passesByUnit, overflowRetryPaths };
|
|
877
|
+
};
|
|
878
|
+
|
|
740
879
|
/**
|
|
741
880
|
* Run the complete host-scheduled fan-out pipeline over one selected
|
|
742
881
|
* changeset snapshot: plan, independent discovery, exact verification, and a
|
|
@@ -748,13 +887,7 @@ export const runFanOutReview = <Provider, ModelProvides, ModelRequires>(
|
|
|
748
887
|
input: FanOutPipelineInput,
|
|
749
888
|
) =>
|
|
750
889
|
Effect.gen(function* () {
|
|
751
|
-
const plan
|
|
752
|
-
const passesByUnit = new Map<string, Array<ReviewDiscoveryPass>>();
|
|
753
|
-
for (const pass of plan.discoveryPasses) {
|
|
754
|
-
const passes = passesByUnit.get(pass.unitId) ?? [];
|
|
755
|
-
passes.push(pass);
|
|
756
|
-
passesByUnit.set(pass.unitId, passes);
|
|
757
|
-
}
|
|
890
|
+
const { plan, passesByUnit, overflowRetryPaths } = scheduleFanOutWork(input);
|
|
758
891
|
const outcomes = yield* Effect.forEach(
|
|
759
892
|
plan.units,
|
|
760
893
|
(unit) => reviewUnit(binding, unit, passesByUnit.get(unit.unitId) ?? [], input),
|
|
@@ -865,6 +998,15 @@ export const runFanOutReview = <Provider, ModelProvides, ModelRequires>(
|
|
|
865
998
|
...plan.undiffablePaths,
|
|
866
999
|
]),
|
|
867
1000
|
].sort(),
|
|
1001
|
+
unreviewedPasses: [
|
|
1002
|
+
...outcomes.flatMap((outcome) => outcome.unreviewedPasses),
|
|
1003
|
+
...(overflowRetryPaths.length === 0
|
|
1004
|
+
? []
|
|
1005
|
+
: (input.retry?.stages.length
|
|
1006
|
+
? input.retry.stages
|
|
1007
|
+
: (["discovery", "specialist", "verification"] as const)
|
|
1008
|
+
).map((stage) => ({ stage, paths: overflowRetryPaths }))),
|
|
1009
|
+
],
|
|
868
1010
|
turns: outcomes.reduce((total, outcome) => total + outcome.turns, 0),
|
|
869
1011
|
} satisfies FanOutPipelineOutcome;
|
|
870
1012
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Crypto, Effect, Encoding } from "effect";
|
|
2
2
|
|
|
3
3
|
import type { ChangedFile } from "./diff.ts";
|
|
4
4
|
|
|
@@ -36,14 +36,14 @@ export const extractFingerprint = (body: string): string | undefined => {
|
|
|
36
36
|
return last;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
const sha256Hex = (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
/** SHA-256 via `Crypto.Crypto`; unavailable crypto is a defect, not an expected failure. */
|
|
40
|
+
const sha256Hex = Effect.fn("sha256Hex")(function* (
|
|
41
|
+
text: string,
|
|
42
|
+
): Effect.fn.Return<string, never, Crypto.Crypto> {
|
|
43
|
+
const crypto = yield* Crypto.Crypto;
|
|
44
|
+
const digest = yield* crypto.digest("SHA-256", new TextEncoder().encode(text)).pipe(Effect.orDie);
|
|
45
|
+
return Encoding.encodeHex(digest);
|
|
46
|
+
});
|
|
47
47
|
|
|
48
48
|
const FIELD = "\u0000";
|
|
49
49
|
const RECORD = "\u0001";
|
|
@@ -80,4 +80,10 @@ const canonicalChangeset = (files: ReadonlyArray<ChangedFile>): string =>
|
|
|
80
80
|
export const computeChangesetFingerprint = (
|
|
81
81
|
files: ReadonlyArray<ChangedFile>,
|
|
82
82
|
signature: string,
|
|
83
|
-
): Effect.Effect<string> =>
|
|
83
|
+
): Effect.Effect<string, never, Crypto.Crypto> =>
|
|
84
|
+
sha256Hex(`${canonicalChangeset(files)}${SECTION}${signature}`);
|
|
85
|
+
|
|
86
|
+
/** Profile fingerprints are SHA-256 over configuration-only signatures. */
|
|
87
|
+
export const computeProfileFingerprint = (
|
|
88
|
+
signature: string,
|
|
89
|
+
): Effect.Effect<string, never, Crypto.Crypto> => sha256Hex(signature);
|
package/src/internal/fixtures.ts
CHANGED
|
@@ -120,6 +120,7 @@ export const staticPriorReviews = (
|
|
|
120
120
|
options: {
|
|
121
121
|
readonly state?: Option.Option<ReviewState> | undefined;
|
|
122
122
|
readonly comparison?: ReviewHeadComparison | undefined;
|
|
123
|
+
readonly treeComparison?: ReviewHeadComparison | undefined;
|
|
123
124
|
} = {},
|
|
124
125
|
): PriorReviews["Service"] =>
|
|
125
126
|
PriorReviews.of({
|
|
@@ -129,6 +130,10 @@ export const staticPriorReviews = (
|
|
|
129
130
|
options.comparison === undefined
|
|
130
131
|
? Effect.fail(PriorReviewLookupFailure.make({ reason: "no fixture comparison" }))
|
|
131
132
|
: Effect.succeed(options.comparison),
|
|
133
|
+
compareTrees: () =>
|
|
134
|
+
options.treeComparison === undefined
|
|
135
|
+
? Effect.fail(PriorReviewLookupFailure.make({ reason: "no fixture tree comparison" }))
|
|
136
|
+
: Effect.succeed(options.treeComparison),
|
|
132
137
|
});
|
|
133
138
|
|
|
134
139
|
/** Layer form for consumers whose Effect explicitly requires `PriorReviews`. */
|
|
@@ -137,6 +142,7 @@ export const staticPriorReviewsLayer = (
|
|
|
137
142
|
options: {
|
|
138
143
|
readonly state?: Option.Option<ReviewState> | undefined;
|
|
139
144
|
readonly comparison?: ReviewHeadComparison | undefined;
|
|
145
|
+
readonly treeComparison?: ReviewHeadComparison | undefined;
|
|
140
146
|
} = {},
|
|
141
147
|
): Layer.Layer<PriorReviews> =>
|
|
142
148
|
Layer.succeed(PriorReviews)(staticPriorReviews(fingerprint, options));
|
package/src/internal/github.ts
CHANGED
|
@@ -130,8 +130,9 @@ const GitHubReviewCommentWire = Schema.Struct({
|
|
|
130
130
|
node_id: Schema.String,
|
|
131
131
|
path: Schema.String,
|
|
132
132
|
body: Schema.String,
|
|
133
|
-
line
|
|
134
|
-
|
|
133
|
+
// Outdated comments omit `line` entirely instead of sending null.
|
|
134
|
+
line: Schema.optionalKey(Schema.NullOr(Schema.Int)),
|
|
135
|
+
original_line: Schema.optionalKey(Schema.NullOr(Schema.Int)),
|
|
135
136
|
start_line: Schema.optionalKey(Schema.NullOr(Schema.Int)),
|
|
136
137
|
original_start_line: Schema.optionalKey(Schema.NullOr(Schema.Int)),
|
|
137
138
|
});
|
|
@@ -578,8 +579,11 @@ export const gitHubReviewRetirementHostLayer: Layer.Layer<
|
|
|
578
579
|
}).pipe(
|
|
579
580
|
Effect.map((comments) =>
|
|
580
581
|
comments.map((comment) => {
|
|
581
|
-
const
|
|
582
|
-
|
|
582
|
+
const positiveLine = (value: number | null | undefined): number | null =>
|
|
583
|
+
value !== undefined && value !== null && value > 0 ? value : null;
|
|
584
|
+
const endLine = positiveLine(comment.line ?? comment.original_line);
|
|
585
|
+
const startLine =
|
|
586
|
+
positiveLine(comment.start_line ?? comment.original_start_line) ?? endLine;
|
|
583
587
|
return RetirableReviewComment.make({
|
|
584
588
|
nodeId: comment.node_id,
|
|
585
589
|
path: comment.path,
|
|
@@ -672,6 +676,15 @@ export class PriorReviews extends Context.Service<
|
|
|
672
676
|
baseSha: string,
|
|
673
677
|
headSha: string,
|
|
674
678
|
) => Effect.Effect<ReviewHeadComparison, PriorReviewLookupFailure>;
|
|
679
|
+
/**
|
|
680
|
+
* Two-dot tree comparison (`base..head`). Used when the reviewed head is
|
|
681
|
+
* not a git ancestor so a rebase or amend can still name the paths whose
|
|
682
|
+
* blob contents actually changed.
|
|
683
|
+
*/
|
|
684
|
+
readonly compareTrees: (
|
|
685
|
+
baseSha: string,
|
|
686
|
+
headSha: string,
|
|
687
|
+
) => Effect.Effect<ReviewHeadComparison, PriorReviewLookupFailure>;
|
|
675
688
|
}
|
|
676
689
|
>()("@effect-agent/pr-review/PriorReviews") {}
|
|
677
690
|
|
|
@@ -775,12 +788,12 @@ export const gitHubPriorReviewsLayer: Layer.Layer<
|
|
|
775
788
|
}
|
|
776
789
|
return { latestFingerprint: latest, latestState };
|
|
777
790
|
}).pipe(Effect.provideService(HttpClient.HttpClient, client));
|
|
778
|
-
const
|
|
791
|
+
const compareCommits = (baseSha: string, headSha: string, separator: "..." | "..") =>
|
|
779
792
|
Effect.gen(function* () {
|
|
780
793
|
const response = yield* HttpClient.execute(
|
|
781
794
|
withCommonHeaders(
|
|
782
795
|
HttpClientRequest.get(
|
|
783
|
-
`${target.apiUrl}/repos/${target.repository}/compare/${encodeURIComponent(baseSha)}
|
|
796
|
+
`${target.apiUrl}/repos/${target.repository}/compare/${encodeURIComponent(baseSha)}${separator}${encodeURIComponent(headSha)}`,
|
|
784
797
|
).pipe(HttpClientRequest.acceptJson),
|
|
785
798
|
target.token,
|
|
786
799
|
),
|
|
@@ -803,6 +816,21 @@ export const gitHubPriorReviewsLayer: Layer.Layer<
|
|
|
803
816
|
truncated: files.length >= MAX_CHANGED_FILES,
|
|
804
817
|
});
|
|
805
818
|
}).pipe(Effect.provideService(HttpClient.HttpClient, client));
|
|
819
|
+
const compareTrees = (baseSha: string, headSha: string) =>
|
|
820
|
+
compareCommits(baseSha, headSha, "..").pipe(
|
|
821
|
+
Effect.map((comparison) =>
|
|
822
|
+
ReviewHeadComparison.make({
|
|
823
|
+
// This is a content snapshot, not a lineage claim. Selection
|
|
824
|
+
// intersects these files with the current PR path set.
|
|
825
|
+
status: comparison.status === "identical" ? "identical" : "ahead",
|
|
826
|
+
baseSha,
|
|
827
|
+
headSha,
|
|
828
|
+
mergeBaseSha: baseSha,
|
|
829
|
+
files: comparison.files,
|
|
830
|
+
truncated: comparison.truncated,
|
|
831
|
+
}),
|
|
832
|
+
),
|
|
833
|
+
);
|
|
806
834
|
return PriorReviews.of({
|
|
807
835
|
latestFingerprint: readMarkers(Option.none()).pipe(
|
|
808
836
|
Effect.map((markers) => markers.latestFingerprint),
|
|
@@ -813,7 +841,8 @@ export const gitHubPriorReviewsLayer: Layer.Layer<
|
|
|
813
841
|
Effect.map((markers) => markers.latestState),
|
|
814
842
|
);
|
|
815
843
|
}),
|
|
816
|
-
compareHeads,
|
|
844
|
+
compareHeads: (baseSha, headSha) => compareCommits(baseSha, headSha, "..."),
|
|
845
|
+
compareTrees,
|
|
817
846
|
});
|
|
818
847
|
}),
|
|
819
848
|
);
|