@effect-agent/pr-review 0.0.1-beta.0 → 0.1.0-beta.11
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 +23 -9
- package/dist/action.d.mts +9 -4
- package/dist/action.mjs +22 -5
- package/dist/action.mjs.map +1 -1
- package/dist/cli.mjs +4 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/{fan-out-Dy84-dIs.d.mts → fan-out-TrA9EUCr.d.mts} +146 -47
- package/dist/{github-bwQ2V-wb.mjs → github-5TCFrxfX.mjs} +366 -50
- package/dist/github-5TCFrxfX.mjs.map +1 -0
- package/dist/index.d.mts +18 -16
- package/dist/index.mjs +3 -3
- package/dist/{providers-DyLJlpJQ.mjs → providers-DobNWMUn.mjs} +12 -8
- package/dist/{providers-DyLJlpJQ.mjs.map → providers-DobNWMUn.mjs.map} +1 -1
- package/dist/testing.d.mts +3 -2
- package/dist/testing.mjs +5 -3
- package/dist/testing.mjs.map +1 -1
- package/package.json +20 -20
- package/src/action.ts +32 -1
- package/src/cli.ts +2 -1
- package/src/index.ts +1 -0
- package/src/internal/action-entry.ts +2 -0
- package/src/internal/fan-out-scripted.ts +2 -1
- package/src/internal/fan-out.ts +76 -73
- package/src/internal/fixtures.ts +5 -1
- package/src/internal/github-env.ts +28 -8
- package/src/internal/github.ts +249 -6
- package/src/internal/providers.ts +4 -1
- package/src/internal/retirement.ts +332 -0
- package/src/internal/review-agent.ts +6 -0
- package/dist/github-bwQ2V-wb.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Context, Effect, Encoding, Layer, Option, Redacted, Result, Schema } from "effect";
|
|
2
|
-
import { Agent, AgentPolicy,
|
|
1
|
+
import { Context, DateTime, Effect, Encoding, Layer, Option, Redacted, Result, Schema } from "effect";
|
|
2
|
+
import { Agent, AgentPolicy, Subagent, SubagentPolicy, SubagentRuntime, ToolExecutionClass } from "effect-agent";
|
|
3
3
|
import { Tool, Toolkit } from "effect/unstable/ai";
|
|
4
4
|
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
|
5
5
|
//#region src/internal/diff.ts
|
|
@@ -414,7 +414,9 @@ const defaultReviewPolicy = AgentPolicy.make({
|
|
|
414
414
|
maxToolCalls: 24,
|
|
415
415
|
maxDuration: "8 minutes",
|
|
416
416
|
toolConcurrency: 2,
|
|
417
|
-
tokenBudget: 3e5
|
|
417
|
+
tokenBudget: 3e5,
|
|
418
|
+
contextTokenLimit: 15e4,
|
|
419
|
+
onExhaustion: "final-answer"
|
|
418
420
|
});
|
|
419
421
|
const PullRequestReviewer = Agent.define("pr-reviewer", {
|
|
420
422
|
input: ReviewMission,
|
|
@@ -549,6 +551,11 @@ const rankAndDedupeFindings = (findings) => {
|
|
|
549
551
|
const MAX_CHILD_FINDINGS = 8;
|
|
550
552
|
/** One child returns at most this many non-anchored concerns. */
|
|
551
553
|
const MAX_CHILD_CONCERNS = 3;
|
|
554
|
+
/**
|
|
555
|
+
* One mandatory diff read plus one bounded context read for every path in a
|
|
556
|
+
* maximum-size unit. Keep the child and delegation reservation aligned.
|
|
557
|
+
*/
|
|
558
|
+
const MAX_FILE_REVIEW_TOOL_CALLS = 24;
|
|
552
559
|
const FileReviewToolkit = Toolkit.make(ReadFileDiff, ReadFile);
|
|
553
560
|
const FileReviewToolkitLayer = FileReviewToolkit.toLayer({
|
|
554
561
|
read_file_diff: readFileDiffHandler,
|
|
@@ -589,10 +596,12 @@ const fileReviewerInstructions = makeFileReviewerInstructions();
|
|
|
589
596
|
/** The default per-unit child execution bounds. */
|
|
590
597
|
const defaultFileReviewerPolicy = AgentPolicy.make({
|
|
591
598
|
maxTurns: 8,
|
|
592
|
-
maxToolCalls:
|
|
593
|
-
maxDuration: "
|
|
599
|
+
maxToolCalls: MAX_FILE_REVIEW_TOOL_CALLS,
|
|
600
|
+
maxDuration: "6 minutes",
|
|
594
601
|
toolConcurrency: 2,
|
|
595
|
-
tokenBudget: 2e5
|
|
602
|
+
tokenBudget: 2e5,
|
|
603
|
+
contextTokenLimit: 15e4,
|
|
604
|
+
onExhaustion: "fail"
|
|
596
605
|
});
|
|
597
606
|
/** The model-decoded delegation parameters: which unit to review. */
|
|
598
607
|
var FileReviewRequest = class extends Schema.Class("@effect-agent/pr-review/FileReviewRequest")({
|
|
@@ -624,8 +633,8 @@ const fileReviewPolicy = SubagentPolicy.make({
|
|
|
624
633
|
maxChildren: 8,
|
|
625
634
|
maxConcurrency: 3,
|
|
626
635
|
maxTurns: 8,
|
|
627
|
-
maxToolCalls:
|
|
628
|
-
maxDuration: "
|
|
636
|
+
maxToolCalls: MAX_FILE_REVIEW_TOOL_CALLS,
|
|
637
|
+
maxDuration: "6 minutes"
|
|
629
638
|
});
|
|
630
639
|
const delegationDescription = "Delegate the review of one planned unit to a bounded file-reviewer child and return its line-anchored findings. Call it exactly once per unit from list_review_units; never retry a failed unit.";
|
|
631
640
|
/**
|
|
@@ -636,23 +645,6 @@ const mapFileReviewChildFailure = (failure) => FileReviewUnitFailed.make({
|
|
|
636
645
|
childErrorTag: failure._tag,
|
|
637
646
|
message: (failure.message ?? "").slice(0, 400)
|
|
638
647
|
});
|
|
639
|
-
/** Exactly the failure union `Subagent.define` declares for this delegation. */
|
|
640
|
-
const FileReviewDelegationFailure = Schema.Union([
|
|
641
|
-
FileReviewUnitFailed,
|
|
642
|
-
SubagentPrestartDenied,
|
|
643
|
-
SubagentBudgetExhausted,
|
|
644
|
-
SubagentProjectionFailure,
|
|
645
|
-
SubagentExecutionFailure,
|
|
646
|
-
ToolCallWaiting,
|
|
647
|
-
SubagentDurabilityError
|
|
648
|
-
]);
|
|
649
|
-
const DelegateFileReview = Tool.make("delegate_file_review", {
|
|
650
|
-
description: delegationDescription,
|
|
651
|
-
parameters: FileReviewRequest,
|
|
652
|
-
success: FileReviewUnitResult,
|
|
653
|
-
failure: FileReviewDelegationFailure,
|
|
654
|
-
failureMode: "return"
|
|
655
|
-
}).addDependency(AgentSpawner).addDependency(RunEventSink).addDependency(SubagentDurability).addDependency(IdGenerator).annotate(ToolExecutionClass, "readonly");
|
|
656
648
|
var ListReviewUnitsQuery = class extends Schema.Class("@effect-agent/pr-review/ListReviewUnitsQuery")({
|
|
657
649
|
/** Explicit constant keeps the zero-choice operation compatible with strict provider schemas. */
|
|
658
650
|
scope: Schema.Literal("all") }) {};
|
|
@@ -669,7 +661,6 @@ const FanOutCoordinatorToolkitLayer = FanOutCoordinatorToolkit.toLayer({ list_re
|
|
|
669
661
|
const source = yield* PullRequestSource;
|
|
670
662
|
return planReviewUnits(yield* source.changedFiles, { totalChangedFiles: (yield* source.metadata).totalChangedFiles });
|
|
671
663
|
}) });
|
|
672
|
-
const FanOutReviewToolkit = Toolkit.make(ListReviewUnits, DelegateFileReview);
|
|
673
664
|
/**
|
|
674
665
|
* Build the coordinator's instructions. The same consumer guidance the
|
|
675
666
|
* children receive is injected between the mission framing and the procedure
|
|
@@ -700,8 +691,10 @@ const defaultFanOutPolicy = AgentPolicy.make({
|
|
|
700
691
|
maxToolCalls: 9,
|
|
701
692
|
maxDuration: "15 minutes",
|
|
702
693
|
toolConcurrency: 3,
|
|
703
|
-
repeatedFailureLimit:
|
|
704
|
-
tokenBudget: 3e5
|
|
694
|
+
repeatedFailureLimit: 3,
|
|
695
|
+
tokenBudget: 3e5,
|
|
696
|
+
contextTokenLimit: 15e4,
|
|
697
|
+
onExhaustion: "final-answer"
|
|
705
698
|
});
|
|
706
699
|
const makeFileReviewerDefinition = (options = {}) => Agent.define("pr-file-reviewer", {
|
|
707
700
|
input: FileReviewBrief,
|
|
@@ -715,25 +708,13 @@ const makeFileReviewerDefinition = (options = {}) => Agent.define("pr-file-revie
|
|
|
715
708
|
surface: "read-only"
|
|
716
709
|
}
|
|
717
710
|
});
|
|
718
|
-
const makeFanOutReviewerDefinition = (options = {}) => Agent.define("pr-fanout-reviewer", {
|
|
719
|
-
input: ReviewMission,
|
|
720
|
-
output: CodeReview,
|
|
721
|
-
instructions: makeFanOutReviewInstructions(options),
|
|
722
|
-
toolkit: FanOutReviewToolkit,
|
|
723
|
-
policy: defaultFanOutPolicy,
|
|
724
|
-
description: "Coordinate one pull-request review by fanning bounded per-unit file reviews out to delegated children and merging their findings into one structured review.",
|
|
725
|
-
metadata: {
|
|
726
|
-
deploymentClass: "E",
|
|
727
|
-
surface: "read-only",
|
|
728
|
-
delegation: "S1-attached"
|
|
729
|
-
}
|
|
730
|
-
});
|
|
731
711
|
const makeFileReviewDelegation = (child) => Subagent.define("delegate_file_review", {
|
|
732
712
|
description: delegationDescription,
|
|
733
713
|
target: child,
|
|
734
714
|
parameters: FileReviewRequest,
|
|
735
715
|
success: FileReviewUnitResult,
|
|
736
716
|
failure: FileReviewUnitFailed,
|
|
717
|
+
failureMode: "return",
|
|
737
718
|
prepareInput: (request) => Effect.succeed(FileReviewBrief.make({
|
|
738
719
|
unitId: request.unitId,
|
|
739
720
|
paths: request.paths,
|
|
@@ -746,13 +727,34 @@ const makeFileReviewDelegation = (child) => Subagent.define("delegate_file_revie
|
|
|
746
727
|
})),
|
|
747
728
|
policy: fileReviewPolicy
|
|
748
729
|
});
|
|
730
|
+
/**
|
|
731
|
+
* The coordinator-facing delegation Tool: the delegation's own first-party
|
|
732
|
+
* contained Tool plus the read-only execution class (the delegated child's
|
|
733
|
+
* whole tool surface is read-only). Effect AI resolves handlers by Tool name,
|
|
734
|
+
* so `SubagentRuntime.layer`'s handler serves this annotated copy unchanged.
|
|
735
|
+
*/
|
|
736
|
+
const delegationToolFor = (delegation) => delegation.tool.annotate(ToolExecutionClass, "readonly");
|
|
737
|
+
const makeFanOutReviewerDefinition = (options, delegation) => Agent.define("pr-fanout-reviewer", {
|
|
738
|
+
input: ReviewMission,
|
|
739
|
+
output: CodeReview,
|
|
740
|
+
instructions: makeFanOutReviewInstructions(options),
|
|
741
|
+
toolkit: Toolkit.make(ListReviewUnits, delegationToolFor(delegation)),
|
|
742
|
+
policy: defaultFanOutPolicy,
|
|
743
|
+
description: "Coordinate one pull-request review by fanning bounded per-unit file reviews out to delegated children and merging their findings into one structured review.",
|
|
744
|
+
metadata: {
|
|
745
|
+
deploymentClass: "E",
|
|
746
|
+
surface: "read-only",
|
|
747
|
+
delegation: "S1-attached"
|
|
748
|
+
}
|
|
749
|
+
});
|
|
749
750
|
/** Build one coherent fan-out suite: child, coordinator, and delegation. */
|
|
750
751
|
const makeFanOutReviewSuite = (options = {}) => {
|
|
751
752
|
const child = makeFileReviewerDefinition({ guidance: options.guidance });
|
|
753
|
+
const delegation = makeFileReviewDelegation(child);
|
|
752
754
|
return {
|
|
753
755
|
child,
|
|
754
|
-
parent: makeFanOutReviewerDefinition(options),
|
|
755
|
-
delegation
|
|
756
|
+
parent: makeFanOutReviewerDefinition(options, delegation),
|
|
757
|
+
delegation
|
|
756
758
|
};
|
|
757
759
|
};
|
|
758
760
|
const defaultSuite = makeFanOutReviewSuite();
|
|
@@ -762,6 +764,16 @@ const FileReviewer = defaultSuite.child;
|
|
|
762
764
|
const FanOutReviewer = defaultSuite.parent;
|
|
763
765
|
/** The default delegation over the default child. */
|
|
764
766
|
const fileReviewDelegation = defaultSuite.delegation;
|
|
767
|
+
/** The default coordinator-facing delegation Tool (first-party contained mode). */
|
|
768
|
+
const DelegateFileReview = delegationToolFor(fileReviewDelegation);
|
|
769
|
+
/** The default coordinator Toolkit. */
|
|
770
|
+
const FanOutReviewToolkit = FanOutReviewer.toolkit;
|
|
771
|
+
/**
|
|
772
|
+
* The contained failure family the delegation can surface as result data
|
|
773
|
+
* (SUB-033), derived from the delegation itself so the coverage decoder can
|
|
774
|
+
* never diverge from what the runtime actually contains.
|
|
775
|
+
*/
|
|
776
|
+
const FileReviewDelegationFailure = fileReviewDelegation.containedFailure;
|
|
765
777
|
/** Runtime wiring: one delegation plus one explicit child Binding. */
|
|
766
778
|
const fanOutHandlersLayerFor = (delegation) => (childBinding) => SubagentRuntime.layer(delegation, childBinding, { mapChildFailure: mapFileReviewChildFailure });
|
|
767
779
|
/** Runtime wiring over the default delegation, mirroring the leaf example. */
|
|
@@ -1082,11 +1094,204 @@ const buildProfileMission = (metadata, files) => ReviewMission.make({
|
|
|
1082
1094
|
changedFileCount: files.length
|
|
1083
1095
|
});
|
|
1084
1096
|
//#endregion
|
|
1097
|
+
//#region src/internal/retirement.ts
|
|
1098
|
+
const PositiveLine = Schema.Int.check(Schema.isGreaterThan(0));
|
|
1099
|
+
/** One previously posted review as observed through the retirement host. */
|
|
1100
|
+
var RetirableReview = class extends Schema.Class("@effect-agent/pr-review/RetirableReview")({
|
|
1101
|
+
reviewId: Schema.Int.check(Schema.isGreaterThan(0)),
|
|
1102
|
+
body: Schema.String.check(Schema.isMaxLength(6e4)),
|
|
1103
|
+
commitSha: Schema.NonEmptyString.check(Schema.isMaxLength(64)),
|
|
1104
|
+
authorNodeId: Schema.NullOr(Schema.NonEmptyString.check(Schema.isMaxLength(200))),
|
|
1105
|
+
submittedAt: Schema.NullOr(Schema.DateTimeUtc)
|
|
1106
|
+
}) {};
|
|
1107
|
+
/** One inline comment attached to a previously posted review. */
|
|
1108
|
+
var RetirableReviewComment = class extends Schema.Class("@effect-agent/pr-review/RetirableReviewComment")({
|
|
1109
|
+
nodeId: Schema.NonEmptyString.check(Schema.isMaxLength(200)),
|
|
1110
|
+
path: Schema.NonEmptyString.check(Schema.isMaxLength(500)),
|
|
1111
|
+
startLine: Schema.NullOr(PositiveLine),
|
|
1112
|
+
endLine: Schema.NullOr(PositiveLine),
|
|
1113
|
+
body: Schema.String.check(Schema.isMaxLength(65536))
|
|
1114
|
+
}) {};
|
|
1115
|
+
/** A GitHub retirement read or mutation failed. */
|
|
1116
|
+
var ReviewRetirementFailure = class extends Schema.TaggedError()("ReviewRetirementFailure", {
|
|
1117
|
+
operation: Schema.String,
|
|
1118
|
+
reason: Schema.String
|
|
1119
|
+
}) {
|
|
1120
|
+
get message() {
|
|
1121
|
+
return `Review retirement operation '${this.operation}' failed: ${this.reason}`;
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
/**
|
|
1125
|
+
* Host-side GitHub operations used by retirement. Domain code never reaches
|
|
1126
|
+
* into REST or GraphQL directly, and deterministic tests substitute this port.
|
|
1127
|
+
*/
|
|
1128
|
+
var ReviewRetirementHost = class extends Context.Service()("@effect-agent/pr-review/ReviewRetirementHost") {};
|
|
1129
|
+
/** Observable cosmetic work completed by one fail-open retirement pass. */
|
|
1130
|
+
var ReviewRetirementReport = class extends Schema.Class("@effect-agent/pr-review/ReviewRetirementReport")({
|
|
1131
|
+
reviewsRetired: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
1132
|
+
findingsResolved: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
1133
|
+
commentsMinimized: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
1134
|
+
failures: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
|
|
1135
|
+
}) {};
|
|
1136
|
+
const findingIdentity = (finding) => `${finding.path}\u0000${finding.startLine}\u0000${finding.endLine}\u0000${finding.title}`;
|
|
1137
|
+
const REVIEW_METADATA_PATTERN = /<!-- effect-agent-pr-review metadata\n[\s\S]*?\n-->/g;
|
|
1138
|
+
const FINGERPRINT_PATTERN = /<!-- effect-agent-pr-review fingerprint=sha256:[0-9a-f]{64} -->/g;
|
|
1139
|
+
const STATE_PATTERN = /<!-- effect-agent-pr-review state-v1:[A-Za-z0-9+/]+={0,2}\.[0-9a-f]{64} -->/g;
|
|
1140
|
+
const RETIRED_ORIGINAL_PATTERN = /<!-- effect-agent-pr-review retired-original:start -->\n([\s\S]*?)\n<!-- effect-agent-pr-review retired-original:end -->/;
|
|
1141
|
+
const MACHINE_COMMENT_PATTERN = new RegExp(`${REVIEW_METADATA_PATTERN.source}|${FINGERPRINT_PATTERN.source}|${STATE_PATTERN.source}`, "g");
|
|
1142
|
+
const VERDICT_CALLOUT_PATTERN = /^(?:> \[!(?:CAUTION|IMPORTANT)\]\n> [^\n]*(?:\n> [^\n]*)*|> (?:ℹ️|✅)[^\n]*)\n*/;
|
|
1143
|
+
const INLINE_FINDING_TITLE_PATTERN = /^\*\*\[(?:🛑 blocking|⚠️ important|💅 nit)\] ([^\n]+)\*\*$/;
|
|
1144
|
+
const MAX_REVIEW_BODY_CHARS = 6e4;
|
|
1145
|
+
/** The host-authored metadata marker is the authority gate for any edit. */
|
|
1146
|
+
const hasReviewMetadataMarker = (body) => /<!-- effect-agent-pr-review metadata\n/.test(body);
|
|
1147
|
+
const machineComments = (body) => Array.from(body.matchAll(MACHINE_COMMENT_PATTERN), (match) => match[0]);
|
|
1148
|
+
const originalVisibleBody = (body) => {
|
|
1149
|
+
const retired = RETIRED_ORIGINAL_PATTERN.exec(body)?.[1];
|
|
1150
|
+
if (retired !== void 0) return retired;
|
|
1151
|
+
return body.replace(MACHINE_COMMENT_PATTERN, "").trim().replace(VERDICT_CALLOUT_PATTERN, "");
|
|
1152
|
+
};
|
|
1153
|
+
const findingLocation = (finding) => `${finding.path}:${finding.startLine}${finding.endLine === finding.startLine ? "" : `-${finding.endLine}`}`;
|
|
1154
|
+
const renderRetiredBody = (input) => {
|
|
1155
|
+
const shortSha = input.currentState.reviewedHeadSha.slice(0, 7);
|
|
1156
|
+
const comments = machineComments(input.priorBody);
|
|
1157
|
+
const original = originalVisibleBody(input.priorBody);
|
|
1158
|
+
const resolved = input.resolvedFindings.length === 0 ? [] : [
|
|
1159
|
+
"### Findings resolved by later review",
|
|
1160
|
+
"",
|
|
1161
|
+
...input.resolvedFindings.map((finding) => `- \`${findingLocation(finding)}\` ~~${finding.title}~~ · resolved at \`${shortSha}\``),
|
|
1162
|
+
""
|
|
1163
|
+
];
|
|
1164
|
+
const prefix = [
|
|
1165
|
+
`> ℹ️ Superseded — ${input.resolvedFindings.length} of ${input.priorState.unresolvedFindings.length} findings resolved at \`${shortSha}\`; see [the latest review](${input.currentReviewUrl}).`,
|
|
1166
|
+
"",
|
|
1167
|
+
"<details>",
|
|
1168
|
+
"<summary>Previous review details</summary>",
|
|
1169
|
+
"",
|
|
1170
|
+
...resolved,
|
|
1171
|
+
"<!-- effect-agent-pr-review retired-original:start -->"
|
|
1172
|
+
];
|
|
1173
|
+
const suffix = [
|
|
1174
|
+
"<!-- effect-agent-pr-review retired-original:end -->",
|
|
1175
|
+
"",
|
|
1176
|
+
"</details>",
|
|
1177
|
+
...comments.length === 0 ? [] : ["", ...comments]
|
|
1178
|
+
];
|
|
1179
|
+
const render = (visible) => [
|
|
1180
|
+
...prefix,
|
|
1181
|
+
visible,
|
|
1182
|
+
...suffix
|
|
1183
|
+
].join("\n");
|
|
1184
|
+
if (render(original).length <= MAX_REVIEW_BODY_CHARS) return render(original);
|
|
1185
|
+
const truncationNotice = "\n\n_Original review content truncated during retirement._";
|
|
1186
|
+
const budget = Math.max(0, MAX_REVIEW_BODY_CHARS - render(truncationNotice).length);
|
|
1187
|
+
return render(`${original.slice(0, budget)}${truncationNotice}`);
|
|
1188
|
+
};
|
|
1189
|
+
/** Compute one prior review's resolved subset and deterministic retired body. */
|
|
1190
|
+
const decideReviewRetirement = (input) => {
|
|
1191
|
+
const current = new Set(input.currentState.unresolvedFindings.map(findingIdentity));
|
|
1192
|
+
const resolvedFindings = input.priorState.unresolvedFindings.filter((finding) => !current.has(findingIdentity(finding)));
|
|
1193
|
+
return {
|
|
1194
|
+
body: renderRetiredBody({
|
|
1195
|
+
...input,
|
|
1196
|
+
resolvedFindings
|
|
1197
|
+
}),
|
|
1198
|
+
resolvedFindings,
|
|
1199
|
+
priorFindingCount: input.priorState.unresolvedFindings.length
|
|
1200
|
+
};
|
|
1201
|
+
};
|
|
1202
|
+
const inlineCommentIdentity = (comment) => {
|
|
1203
|
+
if (comment.startLine === null || comment.endLine === null) return void 0;
|
|
1204
|
+
const firstLine = comment.body.split("\n", 1)[0] ?? "";
|
|
1205
|
+
const title = INLINE_FINDING_TITLE_PATTERN.exec(firstLine)?.[1];
|
|
1206
|
+
return title === void 0 ? void 0 : findingIdentity({
|
|
1207
|
+
path: comment.path,
|
|
1208
|
+
startLine: comment.startLine,
|
|
1209
|
+
endLine: comment.endLine,
|
|
1210
|
+
title
|
|
1211
|
+
});
|
|
1212
|
+
};
|
|
1213
|
+
const failOpen = (effect, fallback, message) => effect.pipe(Effect.catch((error) => Effect.logWarning(`${message}: ${String(error)}`).pipe(Effect.as(fallback))));
|
|
1214
|
+
const isStrictlyOlderReview = (review, input) => {
|
|
1215
|
+
if (review.submittedAt === null) return false;
|
|
1216
|
+
const submittedAt = DateTime.toEpochMillis(review.submittedAt);
|
|
1217
|
+
const currentSubmittedAt = DateTime.toEpochMillis(input.currentSubmittedAt);
|
|
1218
|
+
return submittedAt < currentSubmittedAt || submittedAt === currentSubmittedAt && review.reviewId < input.currentReviewId;
|
|
1219
|
+
};
|
|
1220
|
+
/**
|
|
1221
|
+
* Retire every marker-bearing prior review against the newest posted state.
|
|
1222
|
+
* Every lookup, edit, and minimization is isolated: retirement is cosmetic
|
|
1223
|
+
* and can never change the run or check outcome.
|
|
1224
|
+
*/
|
|
1225
|
+
const retireStaleReviews = Effect.fn("retireStaleReviews")(function* (input) {
|
|
1226
|
+
const host = yield* ReviewRetirementHost;
|
|
1227
|
+
const authenticator = yield* ReviewStateAuthenticator;
|
|
1228
|
+
if (authenticator.status !== "available") {
|
|
1229
|
+
yield* Effect.logWarning("Skipping stale-review retirement because authenticated review state is unavailable.");
|
|
1230
|
+
return ReviewRetirementReport.make({
|
|
1231
|
+
reviewsRetired: 0,
|
|
1232
|
+
findingsResolved: 0,
|
|
1233
|
+
commentsMinimized: 0,
|
|
1234
|
+
failures: 0
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
let failures = 0;
|
|
1238
|
+
let reviewsRetired = 0;
|
|
1239
|
+
let findingsResolved = 0;
|
|
1240
|
+
let commentsMinimized = 0;
|
|
1241
|
+
const reviews = yield* failOpen(host.listReviews, void 0, "Could not list prior reviews");
|
|
1242
|
+
if (reviews === void 0) return ReviewRetirementReport.make({
|
|
1243
|
+
reviewsRetired,
|
|
1244
|
+
findingsResolved,
|
|
1245
|
+
commentsMinimized,
|
|
1246
|
+
failures: 1
|
|
1247
|
+
});
|
|
1248
|
+
for (const review of reviews) {
|
|
1249
|
+
if (review.authorNodeId !== input.currentAuthorNodeId || !isStrictlyOlderReview(review, input) || !hasReviewMetadataMarker(review.body)) continue;
|
|
1250
|
+
const priorState = yield* failOpen(authenticator.extract(review.body), Option.none(), `Could not authenticate prior review ${review.reviewId}`);
|
|
1251
|
+
if (Option.isNone(priorState)) continue;
|
|
1252
|
+
const decision = decideReviewRetirement({
|
|
1253
|
+
priorBody: review.body,
|
|
1254
|
+
priorState: priorState.value,
|
|
1255
|
+
currentState: input.currentState,
|
|
1256
|
+
currentReviewUrl: input.currentReviewUrl
|
|
1257
|
+
});
|
|
1258
|
+
if (yield* failOpen(host.updateBody(review.reviewId, decision.body).pipe(Effect.as(true)), false, `Could not retire prior review ${review.reviewId}`)) {
|
|
1259
|
+
reviewsRetired += 1;
|
|
1260
|
+
findingsResolved += decision.resolvedFindings.length;
|
|
1261
|
+
} else failures += 1;
|
|
1262
|
+
if (decision.resolvedFindings.length === 0) continue;
|
|
1263
|
+
const comments = yield* failOpen(host.listComments(review.reviewId), void 0, `Could not list inline comments for prior review ${review.reviewId}`);
|
|
1264
|
+
if (comments === void 0) {
|
|
1265
|
+
failures += 1;
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
const resolved = new Set(decision.resolvedFindings.map(findingIdentity));
|
|
1269
|
+
for (const comment of comments) {
|
|
1270
|
+
const identity = inlineCommentIdentity(comment);
|
|
1271
|
+
if (identity === void 0 || !resolved.has(identity)) continue;
|
|
1272
|
+
if (yield* failOpen(host.minimizeComment(comment.nodeId).pipe(Effect.as(true)), false, `Could not minimize resolved inline comment ${comment.nodeId}`)) commentsMinimized += 1;
|
|
1273
|
+
else failures += 1;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return ReviewRetirementReport.make({
|
|
1277
|
+
reviewsRetired,
|
|
1278
|
+
findingsResolved,
|
|
1279
|
+
commentsMinimized,
|
|
1280
|
+
failures
|
|
1281
|
+
});
|
|
1282
|
+
});
|
|
1283
|
+
//#endregion
|
|
1085
1284
|
//#region src/internal/github.ts
|
|
1285
|
+
const defaultGraphqlUrl = (apiUrl) => apiUrl === "https://api.github.com" ? "https://api.github.com/graphql" : apiUrl.replace(/\/api\/v3$/, "/api/graphql");
|
|
1086
1286
|
/** Which pull request to review and how to reach the API. */
|
|
1287
|
+
const DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN = "github-actions[bot]";
|
|
1087
1288
|
var GitHubReviewTarget = class GitHubReviewTarget extends Context.Service()("@effect-agent/pr-review/GitHubReviewTarget") {
|
|
1088
1289
|
static layer(config) {
|
|
1089
|
-
return Layer.succeed(this, GitHubReviewTarget.of(
|
|
1290
|
+
return Layer.succeed(this, GitHubReviewTarget.of({
|
|
1291
|
+
...config,
|
|
1292
|
+
graphqlUrl: config.graphqlUrl ?? defaultGraphqlUrl(config.apiUrl),
|
|
1293
|
+
reviewAuthorLogin: config.reviewAuthorLogin ?? "github-actions[bot]"
|
|
1294
|
+
}));
|
|
1090
1295
|
}
|
|
1091
1296
|
};
|
|
1092
1297
|
/** A GitHub API call failed: transport, status, or payload decode. */
|
|
@@ -1121,16 +1326,46 @@ const GitHubFileWire = Schema.Struct({
|
|
|
1121
1326
|
previous_filename: Schema.optionalKey(Schema.String)
|
|
1122
1327
|
});
|
|
1123
1328
|
const GitHubFilesPageWire = Schema.Array(GitHubFileWire);
|
|
1329
|
+
const GitHubActorWire = Schema.Struct({ node_id: Schema.String });
|
|
1124
1330
|
const GitHubReviewWire = Schema.Struct({
|
|
1125
1331
|
id: Schema.Int,
|
|
1126
|
-
html_url: Schema.String
|
|
1332
|
+
html_url: Schema.String,
|
|
1333
|
+
user: Schema.NullOr(GitHubActorWire),
|
|
1334
|
+
submitted_at: Schema.NullOr(Schema.String)
|
|
1335
|
+
});
|
|
1336
|
+
const GitHubRetirableReviewWire = Schema.Struct({
|
|
1337
|
+
id: Schema.Int,
|
|
1338
|
+
body: Schema.NullOr(Schema.String),
|
|
1339
|
+
commit_id: Schema.String,
|
|
1340
|
+
user: Schema.NullOr(GitHubActorWire),
|
|
1341
|
+
submitted_at: Schema.NullOr(Schema.String)
|
|
1342
|
+
});
|
|
1343
|
+
const GitHubRetirableReviewsPageWire = Schema.Array(GitHubRetirableReviewWire);
|
|
1344
|
+
const GitHubReviewCommentWire = Schema.Struct({
|
|
1345
|
+
node_id: Schema.String,
|
|
1346
|
+
path: Schema.String,
|
|
1347
|
+
body: Schema.String,
|
|
1348
|
+
line: Schema.NullOr(Schema.Int),
|
|
1349
|
+
original_line: Schema.NullOr(Schema.Int),
|
|
1350
|
+
start_line: Schema.optionalKey(Schema.NullOr(Schema.Int)),
|
|
1351
|
+
original_start_line: Schema.optionalKey(Schema.NullOr(Schema.Int))
|
|
1127
1352
|
});
|
|
1353
|
+
const GitHubReviewCommentsPageWire = Schema.Array(GitHubReviewCommentWire);
|
|
1354
|
+
const GitHubMinimizeCommentWire = Schema.Struct({
|
|
1355
|
+
data: Schema.optionalKey(Schema.NullOr(Schema.Struct({ minimizeComment: Schema.NullOr(Schema.Struct({ minimizedComment: Schema.NullOr(Schema.Struct({ isMinimized: Schema.Boolean })) })) }))),
|
|
1356
|
+
errors: Schema.optionalKey(Schema.Array(Schema.Struct({ message: Schema.String })))
|
|
1357
|
+
});
|
|
1358
|
+
/** Decode GitHub's external timestamp before it participates in mutation ordering. */
|
|
1359
|
+
const parseGitHubSubmittedAt = (value) => value === null ? null : Option.getOrNull(DateTime.make(value));
|
|
1128
1360
|
/** The publication receipt callers report back to the operator. */
|
|
1129
1361
|
var PublishedReview = class extends Schema.Class("@effect-agent/pr-review/PublishedReview")({
|
|
1130
1362
|
reviewId: Schema.Int,
|
|
1131
1363
|
url: Schema.String,
|
|
1132
1364
|
event: Schema.String,
|
|
1133
|
-
inlineComments: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
|
|
1365
|
+
inlineComments: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
1366
|
+
/** Actor and ordering boundary returned by the create-review response. */
|
|
1367
|
+
authorNodeId: Schema.NullOr(Schema.NonEmptyString.check(Schema.isMaxLength(200))),
|
|
1368
|
+
submittedAt: Schema.NullOr(Schema.DateTimeUtc)
|
|
1134
1369
|
}) {};
|
|
1135
1370
|
/** Posts one planned review; the ONLY mutating operation in this package. */
|
|
1136
1371
|
var ReviewPublisher = class extends Context.Service()("@effect-agent/pr-review/ReviewPublisher") {};
|
|
@@ -1254,10 +1489,90 @@ const gitHubReviewPublisherLayer = Layer.effect(ReviewPublisher)(Effect.gen(func
|
|
|
1254
1489
|
reviewId: wire.id,
|
|
1255
1490
|
url: wire.html_url,
|
|
1256
1491
|
event: plan.event,
|
|
1257
|
-
inlineComments: plan.comments.length
|
|
1492
|
+
inlineComments: plan.comments.length,
|
|
1493
|
+
authorNodeId: wire.user?.node_id ?? null,
|
|
1494
|
+
submittedAt: parseGitHubSubmittedAt(wire.submitted_at)
|
|
1258
1495
|
});
|
|
1259
1496
|
}) });
|
|
1260
1497
|
}));
|
|
1498
|
+
const MAX_RETIREMENT_PAGES = 5;
|
|
1499
|
+
const MINIMIZE_REVIEW_COMMENT_MUTATION = `mutation MinimizeReviewComment($subjectId: ID!) {
|
|
1500
|
+
minimizeComment(input: { subjectId: $subjectId, classifier: OUTDATED }) {
|
|
1501
|
+
minimizedComment { isMinimized }
|
|
1502
|
+
}
|
|
1503
|
+
}`;
|
|
1504
|
+
/** GitHub-backed host operations for cosmetic retirement after publication. */
|
|
1505
|
+
const gitHubReviewRetirementHostLayer = Layer.effect(ReviewRetirementHost)(Effect.gen(function* () {
|
|
1506
|
+
const target = yield* GitHubReviewTarget;
|
|
1507
|
+
const client = yield* HttpClient.HttpClient;
|
|
1508
|
+
const prefix = `${target.apiUrl}/repos/${target.repository}/pulls/${target.number}`;
|
|
1509
|
+
const asRetirementFailure = (operation) => (error) => ReviewRetirementFailure.make({
|
|
1510
|
+
operation,
|
|
1511
|
+
reason: `${error._tag}: ${error.message ?? "request failed"}`.slice(0, 2048)
|
|
1512
|
+
});
|
|
1513
|
+
const executeRetirement = (operation, request) => HttpClient.execute(request).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.mapError(asRetirementFailure(operation)), Effect.provideService(HttpClient.HttpClient, client));
|
|
1514
|
+
const decodeRetirement = (schema, operation) => {
|
|
1515
|
+
const decode = Schema.decodeUnknownEffect(schema);
|
|
1516
|
+
return (response) => response.json.pipe(Effect.mapError(asRetirementFailure(operation)), Effect.flatMap((body) => decode(body).pipe(Effect.mapError(asRetirementFailure(operation)))));
|
|
1517
|
+
};
|
|
1518
|
+
const listPaged = (input) => Effect.gen(function* () {
|
|
1519
|
+
const values = [];
|
|
1520
|
+
const perPage = 100;
|
|
1521
|
+
for (let page = 1; page <= MAX_RETIREMENT_PAGES; page += 1) {
|
|
1522
|
+
const response = yield* executeRetirement(input.operation, withCommonHeaders(HttpClientRequest.get(input.url).pipe(HttpClientRequest.acceptJson, HttpClientRequest.setUrlParams({
|
|
1523
|
+
per_page: String(perPage),
|
|
1524
|
+
page: String(page)
|
|
1525
|
+
})), target.token));
|
|
1526
|
+
const pageValues = yield* input.decode(response);
|
|
1527
|
+
values.push(...pageValues);
|
|
1528
|
+
if (pageValues.length < perPage) return values;
|
|
1529
|
+
}
|
|
1530
|
+
return yield* ReviewRetirementFailure.make({
|
|
1531
|
+
operation: input.operation,
|
|
1532
|
+
reason: `history exceeds the bounded ${MAX_RETIREMENT_PAGES * 100}-item lookup`
|
|
1533
|
+
});
|
|
1534
|
+
});
|
|
1535
|
+
return ReviewRetirementHost.of({
|
|
1536
|
+
listReviews: listPaged({
|
|
1537
|
+
operation: "listReviewsForRetirement",
|
|
1538
|
+
url: `${prefix}/reviews`,
|
|
1539
|
+
decode: decodeRetirement(GitHubRetirableReviewsPageWire, "listReviewsForRetirement")
|
|
1540
|
+
}).pipe(Effect.map((reviews) => reviews.map((review) => RetirableReview.make({
|
|
1541
|
+
reviewId: review.id,
|
|
1542
|
+
body: review.body ?? "",
|
|
1543
|
+
commitSha: review.commit_id,
|
|
1544
|
+
authorNodeId: review.user?.node_id ?? null,
|
|
1545
|
+
submittedAt: parseGitHubSubmittedAt(review.submitted_at)
|
|
1546
|
+
})))),
|
|
1547
|
+
listComments: (reviewId) => listPaged({
|
|
1548
|
+
operation: "listReviewCommentsForRetirement",
|
|
1549
|
+
url: `${prefix}/reviews/${reviewId}/comments`,
|
|
1550
|
+
decode: decodeRetirement(GitHubReviewCommentsPageWire, "listReviewCommentsForRetirement")
|
|
1551
|
+
}).pipe(Effect.map((comments) => comments.map((comment) => {
|
|
1552
|
+
const endLine = comment.line ?? comment.original_line;
|
|
1553
|
+
const startLine = comment.start_line ?? comment.original_start_line ?? endLine;
|
|
1554
|
+
return RetirableReviewComment.make({
|
|
1555
|
+
nodeId: comment.node_id,
|
|
1556
|
+
path: comment.path,
|
|
1557
|
+
startLine,
|
|
1558
|
+
endLine,
|
|
1559
|
+
body: comment.body
|
|
1560
|
+
});
|
|
1561
|
+
}))),
|
|
1562
|
+
updateBody: (reviewId, body) => executeRetirement("updateReview", withCommonHeaders(HttpClientRequest.put(`${prefix}/reviews/${reviewId}`).pipe(HttpClientRequest.acceptJson, HttpClientRequest.bodyJsonUnsafe({ body })), target.token)).pipe(Effect.asVoid),
|
|
1563
|
+
minimizeComment: (nodeId) => Effect.gen(function* () {
|
|
1564
|
+
const response = yield* executeRetirement("minimizeComment", withCommonHeaders(HttpClientRequest.post(target.graphqlUrl).pipe(HttpClientRequest.acceptJson, HttpClientRequest.bodyJsonUnsafe({
|
|
1565
|
+
query: MINIMIZE_REVIEW_COMMENT_MUTATION,
|
|
1566
|
+
variables: { subjectId: nodeId }
|
|
1567
|
+
})), target.token));
|
|
1568
|
+
const wire = yield* decodeRetirement(GitHubMinimizeCommentWire, "minimizeComment")(response);
|
|
1569
|
+
if ((wire.errors?.length ?? 0) > 0 || wire.data?.minimizeComment?.minimizedComment?.isMinimized !== true) return yield* ReviewRetirementFailure.make({
|
|
1570
|
+
operation: "minimizeComment",
|
|
1571
|
+
reason: wire.errors?.map((error) => error.message).join("; ").slice(0, 2048) ?? "GitHub did not confirm comment minimization"
|
|
1572
|
+
});
|
|
1573
|
+
})
|
|
1574
|
+
});
|
|
1575
|
+
}));
|
|
1261
1576
|
/** Reading the pull request's previously posted reviews failed. */
|
|
1262
1577
|
var PriorReviewLookupFailure = class extends Schema.TaggedError()("PriorReviewLookupFailure", { reason: Schema.String }) {
|
|
1263
1578
|
get message() {
|
|
@@ -1296,6 +1611,7 @@ const gitHubPriorReviewsLayer = Layer.effect(PriorReviews)(Effect.gen(function*
|
|
|
1296
1611
|
const target = yield* GitHubReviewTarget;
|
|
1297
1612
|
const client = yield* HttpClient.HttpClient;
|
|
1298
1613
|
const prefix = `${target.apiUrl}/repos/${target.repository}/pulls/${target.number}`;
|
|
1614
|
+
const reviewAuthorLogin = target.reviewAuthorLogin ?? "github-actions[bot]";
|
|
1299
1615
|
const decodePage = Schema.decodeUnknownEffect(GitHubPriorReviewsPageWire);
|
|
1300
1616
|
const asLookupFailure = (error) => PriorReviewLookupFailure.make({ reason: `${error._tag}: ${error.message ?? "request failed"}`.slice(0, 2048) });
|
|
1301
1617
|
const readMarkers = (authenticator) => Effect.gen(function* () {
|
|
@@ -1308,7 +1624,7 @@ const gitHubPriorReviewsLayer = Layer.effect(PriorReviews)(Effect.gen(function*
|
|
|
1308
1624
|
page: String(page)
|
|
1309
1625
|
})), target.token)).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.mapError(asLookupFailure))).json.pipe(Effect.mapError(asLookupFailure), Effect.flatMap((body) => decodePage(body).pipe(Effect.mapError(asLookupFailure))));
|
|
1310
1626
|
for (const wire of wires) {
|
|
1311
|
-
if (wire.user?.login !==
|
|
1627
|
+
if (wire.user?.login.toLowerCase() !== reviewAuthorLogin.toLowerCase() || wire.user.type !== "Bot") continue;
|
|
1312
1628
|
const fingerprint = extractFingerprint(wire.body ?? "");
|
|
1313
1629
|
if (fingerprint !== void 0) latest = Option.some(fingerprint);
|
|
1314
1630
|
if (Option.isSome(authenticator)) {
|
|
@@ -1355,6 +1671,6 @@ const fingerprintUnchanged = (current) => Effect.gen(function* () {
|
|
|
1355
1671
|
return Option.isSome(latest) && latest.value === current;
|
|
1356
1672
|
});
|
|
1357
1673
|
//#endregion
|
|
1358
|
-
export {
|
|
1674
|
+
export { FanOutReviewToolkit as $, ReviewMission as $t, ReviewStateAuthenticator as A, ReviewUnit as At, selectedPullRequestSourceLayer as B, FileDiffView as Bt, MAX_REVIEW_STATE_MARKER_CHARS as C, parsePatch as Cn, makeFanOutReviewInstructions as Ct, ReviewScopeMode as D, MAX_MERGED_FINDINGS as Dt, ReviewMode as E, mapFileReviewChildFailure as Et, buildProfileMission as F, rankAndDedupeFindings as Ft, webCryptoReviewStateAuthenticatorLayer as G, ListChangedFilesQuery as Gt, toStoredFinding as H, FileSliceQuery as Ht, computeProfileFingerprint as I, ChangedFileSummary as It, extractFingerprint as J, PullRequestReviewer as Jt, FINGERPRINT_MARKER_LENGTH as K, MAX_CONCERNS as Kt, fromStoredConcern as L, ChangedFilesView as Lt, ReviewStateMarkerTooLarge as M, ReviewUnitPlan as Mt, StoredReviewConcern as N, UNIT_CHANGED_LINE_BUDGET as Nt, ReviewState as O, MAX_REVIEW_UNITS as Ot, StoredReviewFinding as P, planReviewUnits as Pt, FanOutCoordinatorToolkitLayer as Q, ReviewFinding as Qt, fromStoredFinding as R, CodeReview as Rt, GitCommitSha as S, commentableLines as Sn, fileReviewerInstructions as St, ReviewHeadComparison as T, makeFileReviewerInstructions as Tt, unavailableReviewStateAuthenticatorLayer as U, FindingSeverity as Ut, toStoredConcern as V, FileSlice as Vt, validateReviewState as W, ListChangedFiles as Wt, DelegateFileReview as X, ReadFileDiff as Xt, renderFingerprintMarker as Y, ReadFile as Yt, FanOutCoordinatorToolkit as Z, ReviewConcern as Zt, ReviewRetirementHost as _, normalizeRepoRelativePath as _n, fanOutHandlersLayer as _t, PriorReviews as a, listChangedFilesHandler as an, FileReviewToolkit as at, hasReviewMetadataMarker as b, ChangedPath as bn, fileReviewDelegation as bt, fingerprintUnchanged as c, readFileHandler as cn, FileReviewUnitResult as ct, gitHubReviewPublisherLayer as d, MAX_CHANGED_FILES as dn, ListReviewUnitsQuery as dt, ReviewToolkit as en, FanOutReviewer as et, gitHubReviewRetirementHostLayer as f, MAX_FILE_CHARS as fn, MAX_CHILD_CONCERNS as ft, ReviewRetirementFailure as g, ReviewInputViolation as gn, defaultFileReviewerPolicy as gt, RetirableReviewComment as h, PullRequestSourceFailure as hn, defaultFanOutPolicy as ht, PriorReviewLookupFailure as i, defaultReviewPolicy as in, FileReviewRequest as it, ReviewStateMarker as j, ReviewUnitId as jt, ReviewStateAuthenticationFailure as k, MAX_UNIT_FILES as kt, gitHubPriorReviewsLayer as l, resolveGuidance as ln, FileReviewer as lt, RetirableReview as m, PullRequestSource as mn, MAX_FILE_REVIEW_TOOL_CALLS as mt, GitHubApiFailure as n, ReviewVerdict as nn, FileReviewDelegationFailure as nt, PublishedReview as o, makeReviewInstructions as on, FileReviewToolkitLayer as ot, parseGitHubSubmittedAt as p, PullRequestMetadata as pn, MAX_CHILD_FINDINGS as pt, computeChangesetFingerprint as q, MAX_FINDINGS as qt, GitHubReviewTarget as r, clampMaxFindings as rn, FileReviewReport as rt, ReviewPublisher as s, readFileDiffHandler as sn, FileReviewUnitFailed as st, DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN as t, ReviewToolkitLayer as tn, FileReviewBrief as tt, gitHubPullRequestSourceLayer as u, reviewInstructions as un, ListReviewUnits as ut, ReviewRetirementReport as v, ChangedFile as vn, fanOutHandlersLayerFor as vt, ReviewExecutionContext as w, makeFanOutReviewSuite as wt, retireStaleReviews as x, annotatePatch as xn, fileReviewPolicy as xt, decideReviewRetirement as y, ChangedFileStatus as yn, fanOutReviewInstructions as yt, selectReviewRange as z, FileDiffQuery as zt };
|
|
1359
1675
|
|
|
1360
|
-
//# sourceMappingURL=github-
|
|
1676
|
+
//# sourceMappingURL=github-5TCFrxfX.mjs.map
|