@effect-agent/pr-review 0.1.0-beta.22 → 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 +72 -48
- package/dist/action.d.mts +13 -13
- package/dist/action.mjs +42 -25
- package/dist/action.mjs.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/{fan-out-n-00ppWr.d.mts → fan-out-Bi1v0VaU.d.mts} +314 -307
- package/dist/{github-BgtP7Rdv.mjs → github-C6jrBLA2.mjs} +763 -285
- package/dist/github-C6jrBLA2.mjs.map +1 -0
- package/dist/index.d.mts +84 -126
- package/dist/index.mjs +11 -11
- package/dist/index.mjs.map +1 -1
- package/dist/{providers-BguZK4B_.mjs → providers-2Jao2ZAX.mjs} +173 -585
- package/dist/providers-2Jao2ZAX.mjs.map +1 -0
- package/dist/testing.d.mts +6 -15
- package/dist/testing.mjs +14 -42
- package/dist/testing.mjs.map +1 -1
- package/package.json +2 -2
- package/src/action.ts +58 -34
- package/src/internal/action-entry.ts +0 -1
- package/src/internal/coverage.ts +147 -548
- package/src/internal/factory.ts +33 -54
- package/src/internal/fan-out-scripted.ts +26 -80
- package/src/internal/fan-out.ts +669 -432
- package/src/internal/fingerprint.ts +16 -10
- package/src/internal/fixtures.ts +6 -0
- package/src/internal/github.ts +36 -7
- package/src/internal/profiles.ts +8 -8
- package/src/internal/render.ts +34 -45
- package/src/internal/retirement.ts +3 -1
- package/src/internal/review-state.ts +204 -89
- package/src/internal/review-units.ts +25 -0
- package/src/internal/run.ts +231 -174
- package/src/internal/source.ts +1 -1
- package/dist/github-BgtP7Rdv.mjs.map +0 -1
- package/dist/providers-BguZK4B_.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Context, DateTime, Effect, Encoding, Layer, Option, Redacted, Result, Schema } from "effect";
|
|
2
|
-
import { Agent, AgentPolicy,
|
|
1
|
+
import { Context, Crypto, DateTime, Effect, Encoding, Layer, Option, Redacted, Result, Schema } from "effect";
|
|
2
|
+
import { Agent, AgentPolicy, AgentRuntime, ToolExecutionClass, ToolResultBounds } 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
|
|
@@ -579,6 +579,228 @@ const PullRequestReviewer = Agent.define("pr-reviewer", {
|
|
|
579
579
|
}
|
|
580
580
|
});
|
|
581
581
|
//#endregion
|
|
582
|
+
//#region src/internal/coverage.ts
|
|
583
|
+
var FailedReviewUnit = class extends Schema.Class("@effect-agent/pr-review/FailedReviewUnit")({
|
|
584
|
+
unitId: Schema.NonEmptyString.check(Schema.isMaxLength(32)),
|
|
585
|
+
errorTag: Schema.NonEmptyString.check(Schema.isMaxLength(256))
|
|
586
|
+
}) {};
|
|
587
|
+
/**
|
|
588
|
+
* Compatibility diagnostic retained for callers that consumed the original
|
|
589
|
+
* `coverage` field. New UI and state decisions use ReviewInputCoverage and
|
|
590
|
+
* ReviewAssurance directly.
|
|
591
|
+
*/
|
|
592
|
+
var ReviewCoverage = class extends Schema.Class("@effect-agent/pr-review/ReviewCoverage")({
|
|
593
|
+
status: Schema.Literals(["complete", "incomplete"]),
|
|
594
|
+
requiredPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
|
|
595
|
+
reviewedPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
|
|
596
|
+
unreviewedPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
|
|
597
|
+
failedUnits: Schema.Array(FailedReviewUnit).check(Schema.isMaxLength(8)),
|
|
598
|
+
reasons: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(1e3))).check(Schema.isMaxLength(32))
|
|
599
|
+
}) {};
|
|
600
|
+
var ReviewInputCoverage = class extends Schema.Class("@effect-agent/pr-review/ReviewInputCoverage")({
|
|
601
|
+
status: Schema.Literals(["complete", "incomplete"]),
|
|
602
|
+
requiredPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
|
|
603
|
+
assignedPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
|
|
604
|
+
/** Assigned paths whose model-visible diff was truncated by the evidence bound. */
|
|
605
|
+
partialPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
|
|
606
|
+
unassignedPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
|
|
607
|
+
/**
|
|
608
|
+
* Paths with neither a textual diff nor bounded base/head text (binaries,
|
|
609
|
+
* oversized files). Fail-closed: they keep the status incomplete for as
|
|
610
|
+
* long as they are part of the pull request — an unreviewable change must
|
|
611
|
+
* never authorize a green check. Exclude them deliberately with ignore
|
|
612
|
+
* globs when that is intended.
|
|
613
|
+
*/
|
|
614
|
+
undiffablePaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
|
|
615
|
+
reasons: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(1e3))).check(Schema.isMaxLength(20))
|
|
616
|
+
}) {};
|
|
617
|
+
var FailedReviewPass = class extends Schema.Class("@effect-agent/pr-review/FailedReviewPass")({
|
|
618
|
+
workId: Schema.NonEmptyString.check(Schema.isMaxLength(96)),
|
|
619
|
+
stage: Schema.Literals([
|
|
620
|
+
"discovery",
|
|
621
|
+
"specialist",
|
|
622
|
+
"verification"
|
|
623
|
+
]),
|
|
624
|
+
errorTag: Schema.NonEmptyString.check(Schema.isMaxLength(256))
|
|
625
|
+
}) {};
|
|
626
|
+
/**
|
|
627
|
+
* Settlement of scheduled review work. `incomplete` means reviewer-side work
|
|
628
|
+
* failed after its bounded retry — a machinery gap that is carried forward and
|
|
629
|
+
* retried on the next run, never a statement about the code under review.
|
|
630
|
+
* `unverified` is the flat reviewer's honest constant: one pass with no
|
|
631
|
+
* independent verifier is neither settled assurance nor a failure.
|
|
632
|
+
*/
|
|
633
|
+
var ReviewAssurance = class extends Schema.Class("@effect-agent/pr-review/ReviewAssurance")({
|
|
634
|
+
status: Schema.Literals([
|
|
635
|
+
"settled",
|
|
636
|
+
"incomplete",
|
|
637
|
+
"unverified"
|
|
638
|
+
]),
|
|
639
|
+
requiredGeneralDiscoveryPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
640
|
+
completedGeneralDiscoveryPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
641
|
+
requiredSpecialistPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
642
|
+
completedSpecialistPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
643
|
+
requiredVerificationPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
644
|
+
completedVerificationPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
645
|
+
discoveredCandidates: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
646
|
+
confirmedCandidates: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
647
|
+
rejectedCandidates: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
648
|
+
unsettledCandidates: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
649
|
+
/** Discovery claims discarded for anchors/paths outside their assigned evidence. */
|
|
650
|
+
discardedInvalidFindings: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
651
|
+
failedPasses: Schema.Array(FailedReviewPass).check(Schema.isMaxLength(64)),
|
|
652
|
+
reasons: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(1e3))).check(Schema.isMaxLength(32))
|
|
653
|
+
}) {};
|
|
654
|
+
const toolTrace = (events) => {
|
|
655
|
+
const declared = /* @__PURE__ */ new Map();
|
|
656
|
+
const succeeded = /* @__PURE__ */ new Map();
|
|
657
|
+
const failed = /* @__PURE__ */ new Map();
|
|
658
|
+
for (const event of events) {
|
|
659
|
+
if (event._tag === "ToolCallDeclared") declared.set(event.toolCallId, event);
|
|
660
|
+
if (event._tag === "ToolCallSucceeded") succeeded.set(event.toolCallId, event);
|
|
661
|
+
if (event._tag === "ToolCallFailed") failed.set(event.toolCallId, event);
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
declared,
|
|
665
|
+
succeeded,
|
|
666
|
+
failed
|
|
667
|
+
};
|
|
668
|
+
};
|
|
669
|
+
const sortedUnique = (values) => [...new Set(values)].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
670
|
+
/** Render a bounded, deterministic "label (n): a, b, … (+k more)" reason line. */
|
|
671
|
+
const boundedListReason = (label, values) => {
|
|
672
|
+
const items = sortedUnique(values);
|
|
673
|
+
let rendered = `${label} (${items.length}): `;
|
|
674
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
675
|
+
const item = items[index] ?? "";
|
|
676
|
+
const separator = index === 0 ? "" : ", ";
|
|
677
|
+
const omitted = items.length - index - 1;
|
|
678
|
+
const suffix = omitted === 0 ? "" : ` … (+${omitted} more)`;
|
|
679
|
+
if (`${rendered}${separator}${item}${suffix}`.length > 1e3) {
|
|
680
|
+
const omission = `… (+${items.length - index} more)`;
|
|
681
|
+
return `${rendered.slice(0, 1e3 - omission.length)}${omission}`;
|
|
682
|
+
}
|
|
683
|
+
rendered = `${rendered}${separator}${item}`;
|
|
684
|
+
}
|
|
685
|
+
return rendered;
|
|
686
|
+
};
|
|
687
|
+
const anchorSurfaceAdjusted = (inputCoverage, anchorFiles, totalAnchorFiles) => anchorFiles.length >= totalAnchorFiles ? inputCoverage : ReviewInputCoverage.make({
|
|
688
|
+
...inputCoverage,
|
|
689
|
+
status: "incomplete",
|
|
690
|
+
reasons: [...inputCoverage.reasons, `full pull-request anchor surface exposed ${anchorFiles.length} of ${totalAnchorFiles} required files`]
|
|
691
|
+
});
|
|
692
|
+
/** The flat reviewer's honest constant assurance: one pass, no verifier. */
|
|
693
|
+
const flatAssurance = () => ReviewAssurance.make({
|
|
694
|
+
status: "unverified",
|
|
695
|
+
requiredGeneralDiscoveryPasses: 1,
|
|
696
|
+
completedGeneralDiscoveryPasses: 1,
|
|
697
|
+
requiredSpecialistPasses: 0,
|
|
698
|
+
completedSpecialistPasses: 0,
|
|
699
|
+
requiredVerificationPasses: 0,
|
|
700
|
+
completedVerificationPasses: 0,
|
|
701
|
+
discoveredCandidates: 0,
|
|
702
|
+
confirmedCandidates: 0,
|
|
703
|
+
rejectedCandidates: 0,
|
|
704
|
+
unsettledCandidates: 0,
|
|
705
|
+
discardedInvalidFindings: 0,
|
|
706
|
+
failedPasses: [],
|
|
707
|
+
reasons: ["flat review has no independent candidate-verification pass; use the fan-out pipeline for a settled assurance result"]
|
|
708
|
+
});
|
|
709
|
+
/**
|
|
710
|
+
* Assess one settled flat run from its Run event trace: which required paths
|
|
711
|
+
* received successful bounded diff evidence. This observes tool INPUT
|
|
712
|
+
* assignment only — the host cannot know which evidence the model weighed.
|
|
713
|
+
*/
|
|
714
|
+
const assessFlatReview = (input) => {
|
|
715
|
+
const trace = toolTrace(input.events);
|
|
716
|
+
const requiredPaths = sortedUnique(input.files.map((file) => file.path));
|
|
717
|
+
const assigned = /* @__PURE__ */ new Set();
|
|
718
|
+
const partial = /* @__PURE__ */ new Set();
|
|
719
|
+
const failedPaths = /* @__PURE__ */ new Set();
|
|
720
|
+
for (const [toolCallId, declaration] of trace.declared) {
|
|
721
|
+
if (declaration.toolName !== "read_file_diff") continue;
|
|
722
|
+
const query = Schema.decodeUnknownOption(FileDiffQuery)(declaration.parameters);
|
|
723
|
+
if (Option.isNone(query)) continue;
|
|
724
|
+
const success = trace.succeeded.get(toolCallId);
|
|
725
|
+
if (success !== void 0) {
|
|
726
|
+
assigned.add(query.value.path);
|
|
727
|
+
const view = Schema.decodeUnknownOption(FileDiffView)(success.result);
|
|
728
|
+
if (Option.isSome(view) && view.value.truncated) partial.add(query.value.path);
|
|
729
|
+
}
|
|
730
|
+
if (trace.failed.has(toolCallId)) failedPaths.add(query.value.path);
|
|
731
|
+
}
|
|
732
|
+
const undiffable = new Set(input.files.filter((file) => !isReviewableFile(file)).map((file) => file.path));
|
|
733
|
+
const unassigned = requiredPaths.filter((path) => !undiffable.has(path) && (!assigned.has(path) || failedPaths.has(path)));
|
|
734
|
+
const reasons = [];
|
|
735
|
+
if (input.files.length < input.totalFiles) reasons.push(`review range exposed ${input.files.length} of ${input.totalFiles} required files`);
|
|
736
|
+
if (undiffable.size > 0) reasons.push(boundedListReason("required paths have no reviewable diff or bounded text", undiffable));
|
|
737
|
+
if (failedPaths.size > 0) reasons.push(boundedListReason("diff reads failed", failedPaths));
|
|
738
|
+
if (partial.size > 0) reasons.push(boundedListReason("model-visible diff evidence was truncated", partial));
|
|
739
|
+
if (unassigned.length > 0) reasons.push(boundedListReason("required paths received no successful diff input", unassigned));
|
|
740
|
+
return {
|
|
741
|
+
inputCoverage: anchorSurfaceAdjusted(ReviewInputCoverage.make({
|
|
742
|
+
status: reasons.length === 0 ? "complete" : "incomplete",
|
|
743
|
+
requiredPaths,
|
|
744
|
+
assignedPaths: sortedUnique(assigned),
|
|
745
|
+
partialPaths: sortedUnique(partial),
|
|
746
|
+
unassignedPaths: sortedUnique(unassigned),
|
|
747
|
+
undiffablePaths: sortedUnique(undiffable),
|
|
748
|
+
reasons
|
|
749
|
+
}), input.anchorFiles, input.totalAnchorFiles),
|
|
750
|
+
assurance: flatAssurance(),
|
|
751
|
+
unreviewedPaths: sortedUnique([...unassigned, ...undiffable])
|
|
752
|
+
};
|
|
753
|
+
};
|
|
754
|
+
/**
|
|
755
|
+
* Input coverage of one host-scheduled fan-out plan: which required paths the
|
|
756
|
+
* bounded plan actually assigned complete evidence for. Capacity overflow and
|
|
757
|
+
* undiffable paths are both real gaps; the pipeline carries them so the check
|
|
758
|
+
* stays fail-closed until they are reviewed, removed, or explicitly ignored.
|
|
759
|
+
*/
|
|
760
|
+
const fanOutInputCoverage = (input) => {
|
|
761
|
+
const plan = input.plan;
|
|
762
|
+
const assignedPaths = sortedUnique(plan.units.flatMap((unit) => unit.paths));
|
|
763
|
+
const unassignedPaths = sortedUnique(plan.unassignedPaths);
|
|
764
|
+
const reasons = [];
|
|
765
|
+
if (plan.truncated) reasons.push(`review range exposed ${input.files.length} of ${input.totalFiles} required files`);
|
|
766
|
+
if (plan.undiffablePaths.length > 0) reasons.push(boundedListReason("required paths have no reviewable diff or bounded text", plan.undiffablePaths));
|
|
767
|
+
if (plan.partialEvidencePaths.length > 0) reasons.push(boundedListReason("fan-out capacity left some deterministic evidence shards unassigned", plan.partialEvidencePaths));
|
|
768
|
+
if (plan.unassignedEvidenceShardCount > 0) {
|
|
769
|
+
reasons.push(`${plan.unassignedEvidenceShardCount} deterministic evidence shard(s) exceeded fan-out capacity`);
|
|
770
|
+
reasons.push(boundedListReason(`unassigned evidence shard identifier sample (${plan.unassignedEvidenceShardIds.length} of ${plan.unassignedEvidenceShardCount})`, plan.unassignedEvidenceShardIds));
|
|
771
|
+
}
|
|
772
|
+
if (plan.unassignedPaths.length > 0) reasons.push(boundedListReason("fan-out capacity left paths unassigned", plan.unassignedPaths));
|
|
773
|
+
return anchorSurfaceAdjusted(ReviewInputCoverage.make({
|
|
774
|
+
status: reasons.length === 0 ? "complete" : "incomplete",
|
|
775
|
+
requiredPaths: sortedUnique(input.files.map((file) => file.path)),
|
|
776
|
+
assignedPaths,
|
|
777
|
+
partialPaths: plan.partialEvidencePaths,
|
|
778
|
+
unassignedPaths,
|
|
779
|
+
undiffablePaths: sortedUnique(plan.undiffablePaths),
|
|
780
|
+
reasons
|
|
781
|
+
}), input.anchorFiles, input.totalAnchorFiles);
|
|
782
|
+
};
|
|
783
|
+
/** Compatibility aggregate over the two precise claims. */
|
|
784
|
+
const compatibilityCoverage = (inputCoverage, assurance) => {
|
|
785
|
+
const assuranceIncomplete = assurance.status === "incomplete";
|
|
786
|
+
const failedUnits = /* @__PURE__ */ new Map();
|
|
787
|
+
for (const pass of assurance.failedPasses) {
|
|
788
|
+
const unitId = pass.workId.slice(0, 8);
|
|
789
|
+
if (!failedUnits.has(unitId)) failedUnits.set(unitId, FailedReviewUnit.make({
|
|
790
|
+
unitId,
|
|
791
|
+
errorTag: `${pass.stage}:${pass.errorTag}`
|
|
792
|
+
}));
|
|
793
|
+
}
|
|
794
|
+
return ReviewCoverage.make({
|
|
795
|
+
status: inputCoverage.status === "complete" && !assuranceIncomplete ? "complete" : "incomplete",
|
|
796
|
+
requiredPaths: inputCoverage.requiredPaths,
|
|
797
|
+
reviewedPaths: inputCoverage.assignedPaths,
|
|
798
|
+
unreviewedPaths: sortedUnique([...inputCoverage.partialPaths, ...inputCoverage.unassignedPaths]),
|
|
799
|
+
failedUnits: [...failedUnits.values()].slice(0, 8),
|
|
800
|
+
reasons: [...inputCoverage.reasons, ...assuranceIncomplete ? assurance.reasons : []]
|
|
801
|
+
});
|
|
802
|
+
};
|
|
803
|
+
//#endregion
|
|
582
804
|
//#region src/internal/review-units.ts
|
|
583
805
|
/** The delegation fan-out bound: one parent Run spawns at most this many children. */
|
|
584
806
|
const MAX_REVIEW_UNITS = 8;
|
|
@@ -928,6 +1150,20 @@ const rankAndDedupeFindings = (findings) => {
|
|
|
928
1150
|
return left.startLine - right.startLine;
|
|
929
1151
|
}).slice(0, 20);
|
|
930
1152
|
};
|
|
1153
|
+
/**
|
|
1154
|
+
* The concern analogue of `rankAndDedupeFindings`: dedupe by exact content
|
|
1155
|
+
* keeping the most severe duplicate, rank by severity, and cap at the
|
|
1156
|
+
* `CodeReview` concerns bound.
|
|
1157
|
+
*/
|
|
1158
|
+
const rankAndDedupeConcerns = (concerns) => {
|
|
1159
|
+
const byContent = /* @__PURE__ */ new Map();
|
|
1160
|
+
for (const concern of concerns) {
|
|
1161
|
+
const key = `${concern.title}\u0000${concern.body}`;
|
|
1162
|
+
const previous = byContent.get(key);
|
|
1163
|
+
if (previous === void 0 || severityRank[concern.severity] < severityRank[previous.severity]) byContent.set(key, concern);
|
|
1164
|
+
}
|
|
1165
|
+
return [...byContent.values()].sort((left, right) => severityRank[left.severity] - severityRank[right.severity]).slice(0, 10);
|
|
1166
|
+
};
|
|
931
1167
|
//#endregion
|
|
932
1168
|
//#region src/internal/fan-out.ts
|
|
933
1169
|
/** One discovery pass returns at most this many anchored candidates. */
|
|
@@ -936,8 +1172,14 @@ const MAX_CHILD_FINDINGS = 6;
|
|
|
936
1172
|
const MAX_CHILD_CONCERNS = 3;
|
|
937
1173
|
/** Every unit receives independent general and specialist discovery passes. */
|
|
938
1174
|
const MAX_UNIT_CANDIDATES = 18;
|
|
939
|
-
/**
|
|
1175
|
+
/**
|
|
1176
|
+
* General + specialist discovery for every unit, then one verifier per unit.
|
|
1177
|
+
* The one-retry budget doubles the worst-case child Run count, but the
|
|
1178
|
+
* schedule itself never exceeds this bound.
|
|
1179
|
+
*/
|
|
940
1180
|
const MAX_REVIEW_CHILDREN = 24;
|
|
1181
|
+
/** Bounded structured concurrency across units; passes inside a unit are sequential. */
|
|
1182
|
+
const REVIEW_UNIT_CONCURRENCY = 4;
|
|
941
1183
|
/** Structural minimum for a child that exposes no tools. */
|
|
942
1184
|
const MAX_FILE_REVIEW_TOOL_CALLS = 1;
|
|
943
1185
|
const ReviewWorkPhase = Schema.Literals(["discovery", "verification"]);
|
|
@@ -978,8 +1220,8 @@ var CandidateAssessment = class extends Schema.Class("@effect-agent/pr-review/Ca
|
|
|
978
1220
|
}) {};
|
|
979
1221
|
/**
|
|
980
1222
|
* Exact suggestion settlement shape: a carried suggestion must be settled and
|
|
981
|
-
* nothing else may be.
|
|
982
|
-
* and
|
|
1223
|
+
* nothing else may be. A verification report that violates it is treated as a
|
|
1224
|
+
* misbehaving pass and retried within the pass budget.
|
|
983
1225
|
*/
|
|
984
1226
|
const assessmentSettlesSuggestionExactly = (assessment, candidate) => candidate._tag === "FindingCandidate" && candidate.finding.suggestion !== void 0 ? assessment.suggestion !== void 0 : assessment.suggestion === void 0;
|
|
985
1227
|
/**
|
|
@@ -1007,18 +1249,6 @@ const UnitPaths = Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(S
|
|
|
1007
1249
|
const RiskCategories = Schema.Array(ReviewRiskCategory).check(Schema.isMaxLength(6));
|
|
1008
1250
|
const Candidates = Schema.Array(ReviewCandidate).check(Schema.isMaxLength(18));
|
|
1009
1251
|
const EvidenceShardIds = Schema.Array(ReviewEvidenceShardId).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12));
|
|
1010
|
-
/** Strict-object coordinator request for either discovery or verification. */
|
|
1011
|
-
var FileReviewRequest = class extends Schema.Class("@effect-agent/pr-review/FileReviewRequest")({
|
|
1012
|
-
phase: ReviewWorkPhase,
|
|
1013
|
-
workId: ReviewPassId,
|
|
1014
|
-
unitId: ReviewUnitId,
|
|
1015
|
-
paths: UnitPaths,
|
|
1016
|
-
evidenceShardIds: EvidenceShardIds,
|
|
1017
|
-
perspective: ReviewWorkPerspective,
|
|
1018
|
-
riskCategories: RiskCategories,
|
|
1019
|
-
/** Empty for discovery; the exact discovered set for unit verification. */
|
|
1020
|
-
candidates: Candidates
|
|
1021
|
-
}) {};
|
|
1022
1252
|
/** One complete host-selected evidence shard supplied to a review child. */
|
|
1023
1253
|
var FileReviewEvidence = class extends Schema.Class("@effect-agent/pr-review/FileReviewEvidence")({
|
|
1024
1254
|
shardId: ReviewEvidenceShardId,
|
|
@@ -1042,6 +1272,7 @@ var FileReviewBrief = class extends Schema.Class("@effect-agent/pr-review/FileRe
|
|
|
1042
1272
|
evidenceShardIds: EvidenceShardIds,
|
|
1043
1273
|
perspective: ReviewWorkPerspective,
|
|
1044
1274
|
riskCategories: RiskCategories,
|
|
1275
|
+
/** Empty for discovery; the exact discovered set for unit verification. */
|
|
1045
1276
|
candidates: Candidates,
|
|
1046
1277
|
evidence: Schema.Array(FileReviewEvidence).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12))
|
|
1047
1278
|
}) {};
|
|
@@ -1055,24 +1286,16 @@ var FileReviewReport = class extends Schema.Class("@effect-agent/pr-review/FileR
|
|
|
1055
1286
|
fileSummaries: Schema.Array(WalkthroughEntry).check(Schema.isMaxLength(12)),
|
|
1056
1287
|
assessments: Schema.Array(CandidateAssessment).check(Schema.isMaxLength(18))
|
|
1057
1288
|
}) {};
|
|
1058
|
-
/**
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
assessments: Schema.Array(CandidateAssessment).check(Schema.isMaxLength(18))
|
|
1066
|
-
}) {};
|
|
1067
|
-
var FileReviewUnitFailed = class extends Schema.TaggedError()("FileReviewUnitFailed", {
|
|
1068
|
-
childErrorTag: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
|
|
1069
|
-
message: Schema.String.check(Schema.isMaxLength(400))
|
|
1070
|
-
}) {};
|
|
1071
|
-
var FileReviewWorkRejected = class extends Schema.TaggedError()("FileReviewWorkRejected", {
|
|
1289
|
+
/**
|
|
1290
|
+
* A structurally valid child report that does not answer the scheduled pass:
|
|
1291
|
+
* wrong identity, phase-inapplicable fields, or an inexact assessment set.
|
|
1292
|
+
* Retried once like any other pass fault, because it is model misbehavior,
|
|
1293
|
+
* not evidence about the code under review.
|
|
1294
|
+
*/
|
|
1295
|
+
var ReviewPassMisbehaved = class extends Schema.TaggedError()("ReviewPassMisbehaved", {
|
|
1072
1296
|
workId: ReviewPassId,
|
|
1073
1297
|
reason: Schema.NonEmptyString.check(Schema.isMaxLength(600))
|
|
1074
1298
|
}) {};
|
|
1075
|
-
const FileReviewFailure = Schema.Union([FileReviewUnitFailed, FileReviewWorkRejected]);
|
|
1076
1299
|
const staticGuidanceLines = (guidance) => {
|
|
1077
1300
|
if (guidance === void 0) return [];
|
|
1078
1301
|
return (typeof guidance === "string" ? [guidance] : guidance).filter((line) => line.length > 0);
|
|
@@ -1110,8 +1333,6 @@ const makeFileReviewerInstructions = (options = {}) => (brief) => {
|
|
|
1110
1333
|
};
|
|
1111
1334
|
const fileReviewerInstructions = makeFileReviewerInstructions();
|
|
1112
1335
|
const FileReviewToolkit = Toolkit.empty;
|
|
1113
|
-
/** Compatibility export: the evidence-only child has no handler requirements. */
|
|
1114
|
-
const FileReviewToolkitLayer = Layer.empty;
|
|
1115
1336
|
const defaultFileReviewerPolicy = AgentPolicy.make({
|
|
1116
1337
|
maxTurns: 6,
|
|
1117
1338
|
maxToolCalls: 1,
|
|
@@ -1123,54 +1344,29 @@ const defaultFileReviewerPolicy = AgentPolicy.make({
|
|
|
1123
1344
|
toolResultBounds: ToolResultBounds.make({ maxBytes: REVIEW_TOOL_RESULT_MAX_BYTES }),
|
|
1124
1345
|
onExhaustion: "fail"
|
|
1125
1346
|
});
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
});
|
|
1138
|
-
const sameStrings = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1139
|
-
const rejectWork = (workId, reason) => FileReviewWorkRejected.make({
|
|
1140
|
-
workId,
|
|
1141
|
-
reason
|
|
1142
|
-
});
|
|
1143
|
-
/** Validate coordinator scheduling against the current deterministic plan. */
|
|
1144
|
-
const prepareReviewBrief = (request) => Effect.gen(function* () {
|
|
1145
|
-
const source = yield* PullRequestSource;
|
|
1146
|
-
const mapSourceFailure = (failure) => rejectWork(request.workId, `pull-request source ${failure.operation} failed: ${failure.reason}`.slice(0, 600));
|
|
1147
|
-
const files = yield* source.changedFiles.pipe(Effect.mapError(mapSourceFailure));
|
|
1148
|
-
const plan = planReviewUnits(files, { totalChangedFiles: (yield* source.metadata.pipe(Effect.mapError(mapSourceFailure))).totalChangedFiles });
|
|
1149
|
-
const unit = plan.units.find((candidate) => candidate.unitId === request.unitId);
|
|
1150
|
-
if (unit === void 0 || !sameStrings(request.paths, unit.paths) || !sameStrings(request.evidenceShardIds, unit.evidenceShards.map((shard) => shard.shardId))) return yield* rejectWork(request.workId, "request does not match a host-planned unit");
|
|
1151
|
-
if (request.phase === "discovery") {
|
|
1152
|
-
const pass = plan.discoveryPasses.find((candidate) => candidate.passId === request.workId);
|
|
1153
|
-
if (pass === void 0 || pass.unitId !== request.unitId || !sameStrings(pass.paths, request.paths) || !sameStrings(pass.evidenceShardIds, request.evidenceShardIds) || pass.perspective !== request.perspective || !sameStrings(pass.riskCategories, request.riskCategories) || request.candidates.length !== 0) return yield* rejectWork(request.workId, "discovery request does not match the host plan");
|
|
1154
|
-
} else {
|
|
1155
|
-
if (request.workId !== `${request.unitId}-verification` || request.perspective !== "candidate-verification" || !sameStrings(request.riskCategories, unit.riskCategories) || request.candidates.length === 0) return yield* rejectWork(request.workId, "verification request does not match the host-planned unit");
|
|
1156
|
-
const candidateIds = /* @__PURE__ */ new Set();
|
|
1157
|
-
const candidateSubjects = /* @__PURE__ */ new Set();
|
|
1158
|
-
const allowed = new Set(unit.paths);
|
|
1159
|
-
for (const candidate of request.candidates) {
|
|
1160
|
-
const subjectKey = reviewCandidateSubjectKey(candidate);
|
|
1161
|
-
if (candidateIds.has(candidate.candidateId) || candidateSubjects.has(subjectKey) || candidate.unitId !== unit.unitId || candidate.evidencePaths.some((path) => !allowed.has(path)) || candidate._tag === "FindingCandidate" && !allowed.has(candidate.finding.path)) return yield* rejectWork(request.workId, "verification candidates are duplicated or outside the planned unit");
|
|
1162
|
-
candidateIds.add(candidate.candidateId);
|
|
1163
|
-
candidateSubjects.add(subjectKey);
|
|
1164
|
-
}
|
|
1347
|
+
const makeFileReviewerDefinition = (options = {}) => Agent.define("pr-review-worker", {
|
|
1348
|
+
input: FileReviewBrief,
|
|
1349
|
+
output: FileReviewReport,
|
|
1350
|
+
instructions: makeFileReviewerInstructions(options),
|
|
1351
|
+
toolkit: FileReviewToolkit,
|
|
1352
|
+
policy: defaultFileReviewerPolicy,
|
|
1353
|
+
description: "Perform one bounded discovery or independent candidate-verification pass over host-supplied pull-request evidence.",
|
|
1354
|
+
metadata: {
|
|
1355
|
+
deploymentClass: "E",
|
|
1356
|
+
surface: "read-only",
|
|
1357
|
+
stage: "discovery-verification"
|
|
1165
1358
|
}
|
|
1359
|
+
});
|
|
1360
|
+
const FileReviewer = makeFileReviewerDefinition();
|
|
1361
|
+
const candidateOrdinal = (index) => String(index + 1).padStart(3, "0");
|
|
1362
|
+
/** Rebuild one unit's complete evidence from the same snapshot the plan used. */
|
|
1363
|
+
const unitEvidence = (unit, files) => Effect.gen(function* () {
|
|
1166
1364
|
const byPath = new Map(files.map((file) => [file.path, file]));
|
|
1167
1365
|
const evidence = [];
|
|
1168
1366
|
for (const shard of unit.evidenceShards) {
|
|
1169
1367
|
const file = byPath.get(shard.path);
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
const chunk = chunks[shard.ordinal - 1];
|
|
1173
|
-
if (chunk === void 0 || chunks.length !== shard.total || chunk.annotatedPatch.length !== shard.evidenceChars) return yield* rejectWork(request.workId, `planned evidence shard no longer matches source: ${shard.shardId}`);
|
|
1368
|
+
const chunk = file === void 0 ? void 0 : fileReviewEvidenceChunks(file)[shard.ordinal - 1];
|
|
1369
|
+
if (file === void 0 || chunk === void 0) return yield* Effect.die(/* @__PURE__ */ new Error(`planned evidence shard has no source: ${shard.shardId} (${shard.path})`));
|
|
1174
1370
|
evidence.push(FileReviewEvidence.make({
|
|
1175
1371
|
shardId: shard.shardId,
|
|
1176
1372
|
path: shard.path,
|
|
@@ -1181,171 +1377,364 @@ const prepareReviewBrief = (request) => Effect.gen(function* () {
|
|
|
1181
1377
|
annotatedPatch: chunk.annotatedPatch
|
|
1182
1378
|
}));
|
|
1183
1379
|
}
|
|
1184
|
-
return
|
|
1185
|
-
...request,
|
|
1186
|
-
evidence
|
|
1187
|
-
});
|
|
1380
|
+
return evidence;
|
|
1188
1381
|
});
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1382
|
+
const misbehaved = (workId, reason) => ReviewPassMisbehaved.make({
|
|
1383
|
+
workId,
|
|
1384
|
+
reason: reason.slice(0, 600)
|
|
1385
|
+
});
|
|
1386
|
+
/** Validate that a verification report assesses exactly the scheduled candidates. */
|
|
1387
|
+
const validateVerificationReport = (brief, report) => {
|
|
1388
|
+
if (report.findings.length > 0 || report.concerns.length > 0 || report.fileSummaries.length > 0) return misbehaved(brief.workId, "verification output contained discovery-only fields");
|
|
1389
|
+
const expectedById = new Map(brief.candidates.map((candidate) => [candidate.candidateId, candidate]));
|
|
1390
|
+
const assessedIds = /* @__PURE__ */ new Set();
|
|
1391
|
+
for (const assessment of report.assessments) {
|
|
1392
|
+
const candidate = expectedById.get(assessment.candidateId);
|
|
1393
|
+
if (candidate === void 0 || assessedIds.has(assessment.candidateId)) return misbehaved(brief.workId, "verification output did not assess the exact candidate set");
|
|
1394
|
+
if (!assessmentSettlesSuggestionExactly(assessment, candidate)) return misbehaved(brief.workId, "verification output did not settle suggestion publication exactly");
|
|
1395
|
+
assessedIds.add(assessment.candidateId);
|
|
1396
|
+
}
|
|
1397
|
+
if (assessedIds.size !== expectedById.size) return misbehaved(brief.workId, "verification output did not assess the exact candidate set");
|
|
1398
|
+
};
|
|
1399
|
+
/**
|
|
1400
|
+
* Run one scheduled pass: execute the child, decode its report, and enforce
|
|
1401
|
+
* the pass contract. Any typed fault — child failure, malformed or misdirected
|
|
1402
|
+
* output — is retried once; budget exhaustion is terminal because a retry
|
|
1403
|
+
* would fail the same way. The settled outcome is a value either way, so one
|
|
1404
|
+
* flaky pass can never fail the whole pipeline.
|
|
1405
|
+
*/
|
|
1406
|
+
const runReviewPass = (binding, brief, budget) => Effect.gen(function* () {
|
|
1407
|
+
const result = yield* AgentRuntime.run(binding, brief, {
|
|
1408
|
+
...budget === void 0 ? {} : { budget },
|
|
1409
|
+
estimateCostMicrousd: () => Effect.succeed(500)
|
|
1410
|
+
});
|
|
1411
|
+
const report = yield* Schema.decodeUnknownEffect(FileReviewReport)(result.output).pipe(Effect.mapError((error) => misbehaved(brief.workId, `child report failed to decode: ${error.message}`)));
|
|
1412
|
+
if (report.phase !== brief.phase || report.workId !== brief.workId || report.unitId !== brief.unitId) return yield* misbehaved(brief.workId, "child report identity does not match the scheduled pass");
|
|
1413
|
+
if (brief.phase === "verification") {
|
|
1414
|
+
const violation = validateVerificationReport(brief, report);
|
|
1415
|
+
if (violation !== void 0) return yield* violation;
|
|
1416
|
+
} else if (report.assessments.length > 0) return yield* misbehaved(brief.workId, "discovery output contained verification-only assessments");
|
|
1417
|
+
return {
|
|
1418
|
+
report,
|
|
1419
|
+
turns: result.turns
|
|
1420
|
+
};
|
|
1421
|
+
}).pipe(Effect.scoped, Effect.retry({
|
|
1422
|
+
times: 1,
|
|
1423
|
+
while: (error) => error._tag !== "BudgetExceeded"
|
|
1424
|
+
}), Effect.map((settled) => ({
|
|
1425
|
+
_tag: "settled",
|
|
1426
|
+
...settled
|
|
1427
|
+
})), Effect.catch((error) => Effect.succeed({
|
|
1428
|
+
_tag: "failed",
|
|
1429
|
+
errorTag: String(error._tag).slice(0, 256)
|
|
1430
|
+
})));
|
|
1431
|
+
/**
|
|
1432
|
+
* Keep only findings anchored inside the pass's exact assigned evidence and
|
|
1433
|
+
* concerns bound to unit paths. Everything else is discarded and counted —
|
|
1434
|
+
* an invalid anchor invalidates one claim, never the pass that produced it.
|
|
1435
|
+
*/
|
|
1436
|
+
const harvestDiscovery = (pass, unit, files, anchorFiles, report) => {
|
|
1437
|
+
const allowed = new Set(pass.paths);
|
|
1438
|
+
let discarded = 0;
|
|
1439
|
+
const keptFindings = [];
|
|
1440
|
+
for (const finding of report.findings) {
|
|
1441
|
+
if (!allowed.has(finding.path) || anchorViolation(finding, anchorFiles) !== void 0 || !findingAnchorInUnitEvidence(finding, unit, files)) {
|
|
1442
|
+
discarded += 1;
|
|
1443
|
+
continue;
|
|
1202
1444
|
}
|
|
1203
|
-
|
|
1204
|
-
return Effect.succeed(FileReviewUnitResult.make({
|
|
1205
|
-
phase: report.phase,
|
|
1206
|
-
workId: report.workId,
|
|
1207
|
-
unitId: report.unitId,
|
|
1208
|
-
candidates: [],
|
|
1209
|
-
fileSummaries: [],
|
|
1210
|
-
assessments: report.assessments
|
|
1211
|
-
}));
|
|
1445
|
+
keptFindings.push(finding);
|
|
1212
1446
|
}
|
|
1213
|
-
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
const mapSourceFailure = (failure) => rejectWork(request.workId, `pull-request source ${failure.operation} failed: ${failure.reason}`.slice(0, 600));
|
|
1219
|
-
const files = yield* source.changedFiles.pipe(Effect.mapError(mapSourceFailure));
|
|
1220
|
-
const metadata = yield* source.metadata.pipe(Effect.mapError(mapSourceFailure));
|
|
1221
|
-
const anchorFiles = yield* source.anchorFiles.pipe(Effect.mapError(mapSourceFailure));
|
|
1222
|
-
const unit = planReviewUnits(files, { totalChangedFiles: metadata.totalChangedFiles }).units.find((candidate) => candidate.unitId === request.unitId);
|
|
1223
|
-
if (unit === void 0) return yield* rejectWork(request.workId, "scheduled review unit is no longer available");
|
|
1224
|
-
for (const finding of report.findings) {
|
|
1225
|
-
const violation = anchorViolation(finding, anchorFiles);
|
|
1226
|
-
if (violation !== void 0 || !findingAnchorInUnitEvidence(finding, unit, files)) return yield* rejectWork(request.workId, `discovery finding has no valid anchor in its assigned evidence: ${violation ?? finding.path}`);
|
|
1447
|
+
const keptConcerns = [];
|
|
1448
|
+
for (const candidate of report.concerns) {
|
|
1449
|
+
if (candidate.evidencePaths.some((path) => !allowed.has(path))) {
|
|
1450
|
+
discarded += 1;
|
|
1451
|
+
continue;
|
|
1227
1452
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1453
|
+
keptConcerns.push(candidate);
|
|
1454
|
+
}
|
|
1455
|
+
return {
|
|
1456
|
+
candidates: [...keptFindings.map((finding, index) => FindingCandidate.make({
|
|
1457
|
+
candidateId: `${pass.passId}:finding:${candidateOrdinal(index)}`,
|
|
1458
|
+
workId: pass.passId,
|
|
1459
|
+
unitId: pass.unitId,
|
|
1232
1460
|
finding,
|
|
1233
1461
|
evidencePaths: [finding.path]
|
|
1234
|
-
}))
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
unitId: request.unitId,
|
|
1462
|
+
})), ...keptConcerns.map((candidate, index) => ConcernCandidate.make({
|
|
1463
|
+
candidateId: `${pass.passId}:concern:${candidateOrdinal(index)}`,
|
|
1464
|
+
workId: pass.passId,
|
|
1465
|
+
unitId: pass.unitId,
|
|
1239
1466
|
concern: candidate.concern,
|
|
1240
1467
|
evidencePaths: candidate.evidencePaths
|
|
1241
|
-
}))
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
unitId: report.unitId,
|
|
1246
|
-
candidates: [...findingCandidates, ...concernCandidates],
|
|
1247
|
-
fileSummaries: report.fileSummaries,
|
|
1248
|
-
assessments: []
|
|
1249
|
-
});
|
|
1250
|
-
});
|
|
1468
|
+
}))],
|
|
1469
|
+
fileSummaries: report.fileSummaries.filter((entry) => allowed.has(entry.path)),
|
|
1470
|
+
discarded
|
|
1471
|
+
};
|
|
1251
1472
|
};
|
|
1252
|
-
const
|
|
1253
|
-
const
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1473
|
+
const reviewUnit = (binding, unit, passes, input) => Effect.gen(function* () {
|
|
1474
|
+
const evidence = yield* unitEvidence(unit, input.files);
|
|
1475
|
+
const failedPasses = [];
|
|
1476
|
+
const candidates = [];
|
|
1477
|
+
const subjects = /* @__PURE__ */ new Set();
|
|
1478
|
+
const walkthrough = [];
|
|
1479
|
+
let discardedFindings = 0;
|
|
1480
|
+
let turns = 0;
|
|
1481
|
+
let completedGeneralPasses = 0;
|
|
1482
|
+
let completedSpecialistPasses = 0;
|
|
1483
|
+
for (const pass of passes) {
|
|
1484
|
+
const stage = pass.perspective === "risk-specialist" ? "specialist" : "discovery";
|
|
1485
|
+
const brief = FileReviewBrief.make({
|
|
1486
|
+
phase: "discovery",
|
|
1487
|
+
workId: pass.passId,
|
|
1488
|
+
unitId: pass.unitId,
|
|
1489
|
+
paths: pass.paths,
|
|
1490
|
+
evidenceShardIds: pass.evidenceShardIds,
|
|
1491
|
+
perspective: pass.perspective,
|
|
1492
|
+
riskCategories: pass.riskCategories,
|
|
1493
|
+
candidates: [],
|
|
1494
|
+
evidence
|
|
1495
|
+
});
|
|
1496
|
+
const outcome = yield* runReviewPass(binding, brief, input.budget);
|
|
1497
|
+
if (outcome._tag === "failed") {
|
|
1498
|
+
failedPasses.push(FailedReviewPass.make({
|
|
1499
|
+
workId: pass.passId,
|
|
1500
|
+
stage,
|
|
1501
|
+
errorTag: outcome.errorTag
|
|
1502
|
+
}));
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
turns += outcome.turns;
|
|
1506
|
+
if (stage === "specialist") completedSpecialistPasses += 1;
|
|
1507
|
+
else completedGeneralPasses += 1;
|
|
1508
|
+
const harvest = harvestDiscovery(pass, unit, input.files, input.anchorFiles, outcome.report);
|
|
1509
|
+
discardedFindings += harvest.discarded;
|
|
1510
|
+
if (pass.perspective === "general") walkthrough.push(...harvest.fileSummaries);
|
|
1511
|
+
for (const candidate of harvest.candidates) {
|
|
1512
|
+
const subject = reviewCandidateSubjectKey(candidate);
|
|
1513
|
+
if (subjects.has(subject)) continue;
|
|
1514
|
+
subjects.add(subject);
|
|
1515
|
+
candidates.push(candidate);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
const confirmed = [];
|
|
1519
|
+
let rejectedCandidates = 0;
|
|
1520
|
+
let unsettledCandidates = 0;
|
|
1521
|
+
let completedVerificationPasses = 0;
|
|
1522
|
+
const requiredVerificationPasses = candidates.length > 0 ? 1 : 0;
|
|
1523
|
+
if (candidates.length > 0) {
|
|
1524
|
+
const workId = `${unit.unitId}-verification`;
|
|
1525
|
+
const brief = FileReviewBrief.make({
|
|
1526
|
+
phase: "verification",
|
|
1527
|
+
workId,
|
|
1528
|
+
unitId: unit.unitId,
|
|
1529
|
+
paths: unit.paths,
|
|
1530
|
+
evidenceShardIds: unit.evidenceShards.map((shard) => shard.shardId),
|
|
1531
|
+
perspective: "candidate-verification",
|
|
1532
|
+
riskCategories: unit.riskCategories,
|
|
1533
|
+
candidates,
|
|
1534
|
+
evidence
|
|
1535
|
+
});
|
|
1536
|
+
const outcome = yield* runReviewPass(binding, brief, input.budget);
|
|
1537
|
+
if (outcome._tag === "failed") {
|
|
1538
|
+
unsettledCandidates = candidates.length;
|
|
1539
|
+
failedPasses.push(FailedReviewPass.make({
|
|
1540
|
+
workId,
|
|
1541
|
+
stage: "verification",
|
|
1542
|
+
errorTag: outcome.errorTag
|
|
1543
|
+
}));
|
|
1544
|
+
} else {
|
|
1545
|
+
turns += outcome.turns;
|
|
1546
|
+
completedVerificationPasses = 1;
|
|
1547
|
+
const byId = new Map(candidates.map((candidate) => [candidate.candidateId, candidate]));
|
|
1548
|
+
for (const assessment of outcome.report.assessments) {
|
|
1549
|
+
const candidate = byId.get(assessment.candidateId);
|
|
1550
|
+
if (candidate === void 0) continue;
|
|
1551
|
+
if (assessment.disposition === "confirmed") confirmed.push({
|
|
1552
|
+
assessment,
|
|
1553
|
+
candidate
|
|
1554
|
+
});
|
|
1555
|
+
else rejectedCandidates += 1;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
return {
|
|
1560
|
+
failedPasses,
|
|
1561
|
+
discoveredCandidates: candidates.length,
|
|
1562
|
+
confirmed,
|
|
1563
|
+
rejectedCandidates,
|
|
1564
|
+
unsettledCandidates,
|
|
1565
|
+
discardedFindings,
|
|
1566
|
+
walkthrough,
|
|
1567
|
+
turns,
|
|
1568
|
+
completedGeneralPasses,
|
|
1569
|
+
completedSpecialistPasses,
|
|
1570
|
+
requiredVerificationPasses,
|
|
1571
|
+
completedVerificationPasses,
|
|
1572
|
+
unreviewedPaths: failedPasses.length > 0 ? unit.paths : [],
|
|
1573
|
+
unreviewedPasses: failedPasses.map((pass) => ({
|
|
1574
|
+
stage: pass.stage,
|
|
1575
|
+
paths: unit.paths
|
|
1576
|
+
}))
|
|
1577
|
+
};
|
|
1263
1578
|
});
|
|
1264
|
-
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
const source = yield* PullRequestSource;
|
|
1276
|
-
return planReviewUnits(yield* source.changedFiles, { totalChangedFiles: (yield* source.metadata).totalChangedFiles });
|
|
1277
|
-
}) });
|
|
1278
|
-
const makeFanOutReviewInstructions = (options = {}) => (mission) => {
|
|
1279
|
-
const maxFindings = clampMaxFindings(options.maxFindings);
|
|
1280
|
-
return [
|
|
1281
|
-
`You coordinate the bounded multi-pass review of pull request #${mission.number} ("${mission.title}") in ${mission.repository}.`,
|
|
1282
|
-
mission.body.length > 0 ? `Author description:\n${mission.body}` : "No author description.",
|
|
1283
|
-
...staticGuidanceLines(options.guidance),
|
|
1284
|
-
"1. Call list_review_units exactly once.",
|
|
1285
|
-
"2. For EVERY discoveryPass, call delegate_file_review exactly once with phase \"discovery\", workId=passId, and the pass unitId/paths/evidenceShardIds/perspective/riskCategories verbatim; candidates must be []. Prefer one bounded parallel batch. Never retry.",
|
|
1286
|
-
"3. Group candidates returned by all successful discovery passes by unit. Deterministically deduplicate byte-identical finding or concern payloads, retaining the first candidate in discoveryPass plan order. For every unit with at least one retained candidate, call delegate_file_review exactly once with phase \"verification\", workId \"<unitId>-verification\", perspective \"candidate-verification\", the unit paths/evidenceShardIds/riskCategories, and EVERY retained candidate copied byte-for-byte. Prefer one bounded parallel batch. Never retry.",
|
|
1287
|
-
"4. Verification is authoritative: rejected candidates must not be reported. The host independently reconstructs publishable findings from exact confirmed assessments, so do not select, rewrite, downgrade, or invent findings.",
|
|
1288
|
-
`5. Return ONLY CodeReview JSON. Write a concise summary of completed and failed stages. Set findings=[] and concerns=[]; the host injects exact confirmed candidates. Copy factual fileSummaries into walkthrough without invention. The host publication cap is ${maxFindings}.`,
|
|
1289
|
-
"No configured pipeline can prove absence of defects. Describe settled work, never an exhaustive or defect-free review."
|
|
1290
|
-
].join("\n");
|
|
1579
|
+
const countNoun = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
1580
|
+
const composeSummary = (plan, assurance) => {
|
|
1581
|
+
const requiredDiscovery = assurance.requiredGeneralDiscoveryPasses + assurance.requiredSpecialistPasses;
|
|
1582
|
+
const completedDiscovery = assurance.completedGeneralDiscoveryPasses + assurance.completedSpecialistPasses;
|
|
1583
|
+
const parts = [`Reviewed ${countNoun(plan.totalFiles, "changed file")} across ${countNoun(plan.units.length, "bounded unit")}: ${completedDiscovery}/${requiredDiscovery} discovery and ${assurance.completedVerificationPasses}/${assurance.requiredVerificationPasses} verification pass(es) settled; ${assurance.confirmedCandidates} of ${countNoun(assurance.discoveredCandidates, "discovered candidate")} confirmed by independent verification.`];
|
|
1584
|
+
if (assurance.failedPasses.length > 0) parts.push(`${countNoun(assurance.failedPasses.length, "pass")} did not settle; the affected paths are carried forward and retried on the next run. This is a reviewer-side gap, not a code defect.`);
|
|
1585
|
+
if (assurance.discardedInvalidFindings > 0) parts.push(`${countNoun(assurance.discardedInvalidFindings, "candidate")} discarded for anchors or paths outside the assigned evidence.`);
|
|
1586
|
+
if (plan.undiffablePaths.length > 0) parts.push(`${countNoun(plan.undiffablePaths.length, "path")} had no reviewable textual evidence and keep input coverage incomplete; exclude such paths with ignore globs when that is intended.`);
|
|
1587
|
+
if (plan.unassignedPaths.length > 0 || plan.unassignedEvidenceShardCount > 0) parts.push("The changeset exceeded the bounded fan-out capacity; unassigned scope is reported under input coverage.");
|
|
1588
|
+
parts.push("No configured pipeline can prove absence of defects; this describes settled work only.");
|
|
1589
|
+
return parts.join(" ").slice(0, 4e3);
|
|
1291
1590
|
};
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
});
|
|
1303
|
-
const makeFileReviewerDefinition = (options = {}) => Agent.define("pr-review-worker", {
|
|
1304
|
-
input: FileReviewBrief,
|
|
1305
|
-
output: FileReviewReport,
|
|
1306
|
-
instructions: makeFileReviewerInstructions(options),
|
|
1307
|
-
toolkit: FileReviewToolkit,
|
|
1308
|
-
policy: defaultFileReviewerPolicy,
|
|
1309
|
-
description: "Perform one bounded discovery or independent candidate-verification pass over host-supplied pull-request evidence.",
|
|
1310
|
-
metadata: {
|
|
1311
|
-
deploymentClass: "E",
|
|
1312
|
-
surface: "read-only",
|
|
1313
|
-
stage: "discovery-verification"
|
|
1591
|
+
const remapPlanUnitIds = (plan, offset) => {
|
|
1592
|
+
if (offset === 0) return plan;
|
|
1593
|
+
const units = plan.units.map((unit, index) => ReviewUnit.make({
|
|
1594
|
+
...unit,
|
|
1595
|
+
unitId: `unit-${String(offset + index + 1).padStart(3, "0")}`
|
|
1596
|
+
}));
|
|
1597
|
+
const mappedIds = /* @__PURE__ */ new Map();
|
|
1598
|
+
for (const [index, unit] of plan.units.entries()) {
|
|
1599
|
+
const remapped = units[index];
|
|
1600
|
+
if (remapped !== void 0) mappedIds.set(unit.unitId, remapped.unitId);
|
|
1314
1601
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1602
|
+
return ReviewUnitPlan.make({
|
|
1603
|
+
...plan,
|
|
1604
|
+
units,
|
|
1605
|
+
discoveryPasses: plan.discoveryPasses.map((pass) => {
|
|
1606
|
+
const unitId = mappedIds.get(pass.unitId) ?? pass.unitId;
|
|
1607
|
+
return ReviewDiscoveryPass.make({
|
|
1608
|
+
...pass,
|
|
1609
|
+
unitId,
|
|
1610
|
+
passId: `${unitId}${pass.passId.slice(pass.unitId.length)}`
|
|
1611
|
+
});
|
|
1612
|
+
})
|
|
1613
|
+
});
|
|
1614
|
+
};
|
|
1615
|
+
const scheduleFanOutWork = (input) => {
|
|
1616
|
+
const retryPathSet = new Set(input.retry?.paths ?? []);
|
|
1617
|
+
const retryStages = new Set(input.retry?.stages ?? []);
|
|
1618
|
+
if (retryPathSet.size === 0) {
|
|
1619
|
+
const plan = planReviewUnits(input.files, { totalChangedFiles: input.totalChangedFiles });
|
|
1620
|
+
const passesByUnit = /* @__PURE__ */ new Map();
|
|
1621
|
+
for (const pass of plan.discoveryPasses) {
|
|
1622
|
+
const passes = passesByUnit.get(pass.unitId) ?? [];
|
|
1623
|
+
passes.push(pass);
|
|
1624
|
+
passesByUnit.set(pass.unitId, passes);
|
|
1625
|
+
}
|
|
1626
|
+
return {
|
|
1627
|
+
plan,
|
|
1628
|
+
passesByUnit,
|
|
1629
|
+
overflowRetryPaths: []
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
const freshFiles = input.files.filter((file) => !retryPathSet.has(file.path));
|
|
1633
|
+
const retryFiles = input.files.filter((file) => retryPathSet.has(file.path));
|
|
1634
|
+
const freshPlan = planReviewUnits(freshFiles, { totalChangedFiles: input.totalChangedFiles });
|
|
1635
|
+
const retryPlan = remapPlanUnitIds(planReviewUnits(retryFiles, { totalChangedFiles: input.totalChangedFiles }), freshPlan.units.length);
|
|
1636
|
+
const acceptedFresh = freshPlan.units.slice(0, 8);
|
|
1637
|
+
const acceptedRetry = retryPlan.units.slice(0, Math.max(0, 8 - acceptedFresh.length));
|
|
1638
|
+
const overflowRetryPaths = retryPlan.units.slice(acceptedRetry.length).flatMap((unit) => [...unit.paths]);
|
|
1639
|
+
const retryPassFilter = (pass) => {
|
|
1640
|
+
const stage = pass.perspective === "risk-specialist" ? "specialist" : "discovery";
|
|
1641
|
+
return retryStages.has(stage);
|
|
1642
|
+
};
|
|
1643
|
+
const acceptedRetryIds = new Set(acceptedRetry.map((unit) => unit.unitId));
|
|
1644
|
+
let retryPasses = retryPlan.discoveryPasses.filter((pass) => acceptedRetryIds.has(pass.unitId) && retryPassFilter(pass));
|
|
1645
|
+
if (retryStages.has("verification") && !retryStages.has("discovery") && !retryStages.has("specialist")) retryPasses = retryPlan.discoveryPasses.filter((pass) => acceptedRetryIds.has(pass.unitId));
|
|
1646
|
+
const acceptedFreshIds = new Set(acceptedFresh.map((unit) => unit.unitId));
|
|
1647
|
+
const discoveryPasses = [...freshPlan.discoveryPasses.filter((pass) => acceptedFreshIds.has(pass.unitId)), ...retryPasses];
|
|
1648
|
+
const plan = ReviewUnitPlan.make({
|
|
1649
|
+
totalFiles: input.files.length,
|
|
1650
|
+
truncated: freshPlan.truncated || retryPlan.truncated,
|
|
1651
|
+
units: [...acceptedFresh, ...acceptedRetry],
|
|
1652
|
+
discoveryPasses,
|
|
1653
|
+
undiffablePaths: [.../* @__PURE__ */ new Set([...freshPlan.undiffablePaths, ...retryPlan.undiffablePaths])].sort(),
|
|
1654
|
+
partialEvidencePaths: [.../* @__PURE__ */ new Set([...freshPlan.partialEvidencePaths, ...retryPlan.partialEvidencePaths])].sort(),
|
|
1655
|
+
unassignedEvidenceShardCount: freshPlan.unassignedEvidenceShardCount + retryPlan.unassignedEvidenceShardCount,
|
|
1656
|
+
unassignedEvidenceShardIds: [...freshPlan.unassignedEvidenceShardIds, ...retryPlan.unassignedEvidenceShardIds].slice(0, 96),
|
|
1657
|
+
unassignedPaths: [.../* @__PURE__ */ new Set([
|
|
1658
|
+
...freshPlan.unassignedPaths,
|
|
1659
|
+
...retryPlan.unassignedPaths,
|
|
1660
|
+
...overflowRetryPaths
|
|
1661
|
+
])].sort()
|
|
1662
|
+
});
|
|
1663
|
+
const passesByUnit = /* @__PURE__ */ new Map();
|
|
1664
|
+
for (const pass of discoveryPasses) {
|
|
1665
|
+
const passes = passesByUnit.get(pass.unitId) ?? [];
|
|
1666
|
+
passes.push(pass);
|
|
1667
|
+
passesByUnit.set(pass.unitId, passes);
|
|
1329
1668
|
}
|
|
1330
|
-
});
|
|
1331
|
-
const makeFanOutReviewSuite = (options = {}) => {
|
|
1332
|
-
const child = makeFileReviewerDefinition({ guidance: options.guidance });
|
|
1333
|
-
const delegation = makeFileReviewDelegation(child);
|
|
1334
1669
|
return {
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1670
|
+
plan,
|
|
1671
|
+
passesByUnit,
|
|
1672
|
+
overflowRetryPaths
|
|
1338
1673
|
};
|
|
1339
1674
|
};
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
const
|
|
1347
|
-
const
|
|
1348
|
-
const
|
|
1675
|
+
/**
|
|
1676
|
+
* Run the complete host-scheduled fan-out pipeline over one selected
|
|
1677
|
+
* changeset snapshot: plan, independent discovery, exact verification, and a
|
|
1678
|
+
* deterministic host-composed CodeReview from verifier-confirmed candidates
|
|
1679
|
+
* only. The verdict is derived from confirmed severities, never model prose.
|
|
1680
|
+
*/
|
|
1681
|
+
const runFanOutReview = (binding, input) => Effect.gen(function* () {
|
|
1682
|
+
const { plan, passesByUnit, overflowRetryPaths } = scheduleFanOutWork(input);
|
|
1683
|
+
const outcomes = yield* Effect.forEach(plan.units, (unit) => reviewUnit(binding, unit, passesByUnit.get(unit.unitId) ?? [], input), { concurrency: 4 });
|
|
1684
|
+
const failedPasses = outcomes.flatMap((outcome) => outcome.failedPasses);
|
|
1685
|
+
const unsettledCandidates = outcomes.reduce((total, outcome) => total + outcome.unsettledCandidates, 0);
|
|
1686
|
+
const reasons = [];
|
|
1687
|
+
if (failedPasses.length > 0) reasons.push(boundedListReason("configured review passes did not settle", failedPasses.map((pass) => `${pass.workId} (${pass.errorTag})`)));
|
|
1688
|
+
if (unsettledCandidates > 0) reasons.push(`${unsettledCandidates} discovered candidate(s) did not receive exact verification`);
|
|
1689
|
+
const requiredSpecialistPasses = plan.discoveryPasses.filter((pass) => pass.perspective === "risk-specialist").length;
|
|
1690
|
+
const confirmed = outcomes.flatMap((outcome) => outcome.confirmed);
|
|
1691
|
+
const assurance = ReviewAssurance.make({
|
|
1692
|
+
status: reasons.length === 0 ? "settled" : "incomplete",
|
|
1693
|
+
requiredGeneralDiscoveryPasses: plan.discoveryPasses.length - requiredSpecialistPasses,
|
|
1694
|
+
completedGeneralDiscoveryPasses: outcomes.reduce((total, outcome) => total + outcome.completedGeneralPasses, 0),
|
|
1695
|
+
requiredSpecialistPasses,
|
|
1696
|
+
completedSpecialistPasses: outcomes.reduce((total, outcome) => total + outcome.completedSpecialistPasses, 0),
|
|
1697
|
+
requiredVerificationPasses: outcomes.reduce((total, outcome) => total + outcome.requiredVerificationPasses, 0),
|
|
1698
|
+
completedVerificationPasses: outcomes.reduce((total, outcome) => total + outcome.completedVerificationPasses, 0),
|
|
1699
|
+
discoveredCandidates: outcomes.reduce((total, outcome) => total + outcome.discoveredCandidates, 0),
|
|
1700
|
+
confirmedCandidates: confirmed.length,
|
|
1701
|
+
rejectedCandidates: outcomes.reduce((total, outcome) => total + outcome.rejectedCandidates, 0),
|
|
1702
|
+
unsettledCandidates,
|
|
1703
|
+
discardedInvalidFindings: outcomes.reduce((total, outcome) => total + outcome.discardedFindings, 0),
|
|
1704
|
+
failedPasses,
|
|
1705
|
+
reasons
|
|
1706
|
+
});
|
|
1707
|
+
const findings = rankAndDedupeFindings(confirmed.flatMap(({ assessment, candidate }) => candidate._tag === "FindingCandidate" ? [confirmedFindingForPublication(assessment, candidate)] : []));
|
|
1708
|
+
const concerns = rankAndDedupeConcerns(confirmed.flatMap(({ candidate }) => candidate._tag === "ConcernCandidate" ? [candidate.concern] : []));
|
|
1709
|
+
const walkthrough = outcomes.flatMap((outcome) => outcome.walkthrough);
|
|
1710
|
+
const blocking = findings.some((finding) => finding.severity === "blocking") || concerns.some((concern) => concern.severity === "blocking");
|
|
1711
|
+
return {
|
|
1712
|
+
review: CodeReview.make({
|
|
1713
|
+
summary: composeSummary(plan, assurance),
|
|
1714
|
+
verdict: blocking ? "request-changes" : findings.length > 0 || concerns.length > 0 ? "comment" : "approve",
|
|
1715
|
+
findings,
|
|
1716
|
+
...concerns.length === 0 ? {} : { concerns },
|
|
1717
|
+
...walkthrough.length === 0 ? {} : { walkthrough }
|
|
1718
|
+
}),
|
|
1719
|
+
assurance,
|
|
1720
|
+
plan,
|
|
1721
|
+
unreviewedPaths: [.../* @__PURE__ */ new Set([
|
|
1722
|
+
...outcomes.flatMap((outcome) => outcome.unreviewedPaths),
|
|
1723
|
+
...plan.unassignedPaths,
|
|
1724
|
+
...plan.partialEvidencePaths,
|
|
1725
|
+
...plan.undiffablePaths
|
|
1726
|
+
])].sort(),
|
|
1727
|
+
unreviewedPasses: [...outcomes.flatMap((outcome) => outcome.unreviewedPasses), ...overflowRetryPaths.length === 0 ? [] : (input.retry?.stages.length ? input.retry.stages : [
|
|
1728
|
+
"discovery",
|
|
1729
|
+
"specialist",
|
|
1730
|
+
"verification"
|
|
1731
|
+
]).map((stage) => ({
|
|
1732
|
+
stage,
|
|
1733
|
+
paths: overflowRetryPaths
|
|
1734
|
+
}))],
|
|
1735
|
+
turns: outcomes.reduce((total, outcome) => total + outcome.turns, 0)
|
|
1736
|
+
};
|
|
1737
|
+
});
|
|
1349
1738
|
//#endregion
|
|
1350
1739
|
//#region src/internal/fingerprint.ts
|
|
1351
1740
|
const MARKER_PREFIX = "<!-- effect-agent-pr-review fingerprint=sha256:";
|
|
@@ -1361,10 +1750,10 @@ const extractFingerprint = (body) => {
|
|
|
1361
1750
|
for (const match of body.matchAll(MARKER_PATTERN)) last = match[1];
|
|
1362
1751
|
return last;
|
|
1363
1752
|
};
|
|
1364
|
-
/**
|
|
1365
|
-
const sha256Hex =
|
|
1366
|
-
const digest =
|
|
1367
|
-
return
|
|
1753
|
+
/** SHA-256 via `Crypto.Crypto`; unavailable crypto is a defect, not an expected failure. */
|
|
1754
|
+
const sha256Hex = Effect.fn("sha256Hex")(function* (text) {
|
|
1755
|
+
const digest = yield* (yield* Crypto.Crypto).digest("SHA-256", new TextEncoder().encode(text)).pipe(Effect.orDie);
|
|
1756
|
+
return Encoding.encodeHex(digest);
|
|
1368
1757
|
});
|
|
1369
1758
|
const FIELD = "\0";
|
|
1370
1759
|
const RECORD = "";
|
|
@@ -1388,6 +1777,8 @@ const canonicalChangeset = (files) => files.map((file) => `${file.path}${FIELD}$
|
|
|
1388
1777
|
* not carry.
|
|
1389
1778
|
*/
|
|
1390
1779
|
const computeChangesetFingerprint = (files, signature) => sha256Hex(`${canonicalChangeset(files)}${SECTION}${signature}`);
|
|
1780
|
+
/** Profile fingerprints are SHA-256 over configuration-only signatures. */
|
|
1781
|
+
const computeProfileFingerprint = (signature) => sha256Hex(signature);
|
|
1391
1782
|
//#endregion
|
|
1392
1783
|
//#region src/internal/review-state.ts
|
|
1393
1784
|
const ReviewMode = Schema.Literals(["incremental", "final"]);
|
|
@@ -1410,16 +1801,34 @@ var StoredReviewConcern = class extends Schema.Class("@effect-agent/pr-review/St
|
|
|
1410
1801
|
title: Schema.NonEmptyString.check(Schema.isMaxLength(120)),
|
|
1411
1802
|
body: StoredText
|
|
1412
1803
|
}) {};
|
|
1804
|
+
/** The carried-scope bound; a run that cannot fit its leftovers publishes no state. */
|
|
1805
|
+
const MAX_STORED_UNREVIEWED_PATHS = 100;
|
|
1806
|
+
/** Failed-pass records stored beside the leftover paths; one per unit stage. */
|
|
1807
|
+
const MAX_STORED_UNREVIEWED_PASSES = 24;
|
|
1808
|
+
/** Stages a leftover path may need retried without a second general discovery. */
|
|
1809
|
+
const UnreviewedStage = Schema.Literals([
|
|
1810
|
+
"discovery",
|
|
1811
|
+
"specialist",
|
|
1812
|
+
"verification"
|
|
1813
|
+
]);
|
|
1814
|
+
/** One failed fan-out pass whose paths should be retried, not rediscovered. */
|
|
1815
|
+
var StoredUnreviewedPass = class extends Schema.Class("@effect-agent/pr-review/StoredUnreviewedPass")({
|
|
1816
|
+
stage: UnreviewedStage,
|
|
1817
|
+
paths: Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12))
|
|
1818
|
+
}) {};
|
|
1413
1819
|
/**
|
|
1414
|
-
* Versioned state embedded
|
|
1415
|
-
*
|
|
1416
|
-
*
|
|
1417
|
-
*
|
|
1418
|
-
*
|
|
1419
|
-
*
|
|
1820
|
+
* Versioned state embedded after EVERY completed run that can be signed. The
|
|
1821
|
+
* head plus full-scope fingerprint forms an incremental baseline; an absent
|
|
1822
|
+
* unresolved item never means the path is defect-free. `unreviewedPaths`
|
|
1823
|
+
* carries retryable review gaps (failed passes) forward so the next
|
|
1824
|
+
* incremental run re-reviews exactly them plus the new delta — the baseline
|
|
1825
|
+
* advances monotonically instead of freezing on one flaky pass and reopening
|
|
1826
|
+
* the whole post-baseline scope. The `acceptedScopeFingerprint` name is
|
|
1827
|
+
* retained for wire compatibility. Storing hundreds of path strings
|
|
1828
|
+
* separately would not fit GitHub's bounded review body in the worst case.
|
|
1420
1829
|
*/
|
|
1421
1830
|
var ReviewState = class extends Schema.Class("@effect-agent/pr-review/ReviewState")({
|
|
1422
|
-
version: Schema.Literal(
|
|
1831
|
+
version: Schema.Literal(2),
|
|
1423
1832
|
repository: Schema.NonEmptyString.check(Schema.isMaxLength(200)),
|
|
1424
1833
|
pullRequestNumber: Schema.Int.check(Schema.isGreaterThan(0)),
|
|
1425
1834
|
baseRef: Schema.NonEmptyString.check(Schema.isMaxLength(300)),
|
|
@@ -1434,6 +1843,20 @@ var ReviewState = class extends Schema.Class("@effect-agent/pr-review/ReviewStat
|
|
|
1434
1843
|
})),
|
|
1435
1844
|
unresolvedFindings: Schema.Array(StoredReviewFinding).check(Schema.isMaxLength(20)),
|
|
1436
1845
|
unresolvedConcerns: Schema.Array(StoredReviewConcern).check(Schema.isMaxLength(10)),
|
|
1846
|
+
/** Retryable review gaps carried into the next incremental run's scope. */
|
|
1847
|
+
unreviewedPaths: Schema.Array(ChangedPath).check(Schema.isMaxLength(100)),
|
|
1848
|
+
/**
|
|
1849
|
+
* Which failed pass produced those leftovers. Absent on state-v2 markers
|
|
1850
|
+
* written before this field existed; those leftovers still re-enter scope
|
|
1851
|
+
* but cannot skip rediscovery. Present (including empty) on new markers.
|
|
1852
|
+
*/
|
|
1853
|
+
unreviewedPasses: Schema.optionalKey(Schema.Array(StoredUnreviewedPass).check(Schema.isMaxLength(24))),
|
|
1854
|
+
/**
|
|
1855
|
+
* True only when the producing run had complete input coverage, no
|
|
1856
|
+
* unsettled pass, and nothing carried. Skip-unchanged authority: an
|
|
1857
|
+
* unchanged patch may skip re-review only over a settled state.
|
|
1858
|
+
*/
|
|
1859
|
+
settled: Schema.Boolean,
|
|
1437
1860
|
lastReviewMode: ReviewScopeMode
|
|
1438
1861
|
}) {};
|
|
1439
1862
|
const toStoredFinding = (finding) => StoredReviewFinding.make({
|
|
@@ -1462,12 +1885,12 @@ const fromStoredConcern = (concern) => ReviewConcern.make({
|
|
|
1462
1885
|
title: concern.title,
|
|
1463
1886
|
body: concern.body
|
|
1464
1887
|
});
|
|
1465
|
-
const STATE_MARKER_PREFIX = "<!-- effect-agent-pr-review state-
|
|
1888
|
+
const STATE_MARKER_PREFIX = "<!-- effect-agent-pr-review state-v2:";
|
|
1466
1889
|
const STATE_MARKER_SUFFIX = " -->";
|
|
1467
|
-
const STATE_MARKER_PATTERN = /(?:^|\n)<!-- effect-agent-pr-review state-
|
|
1468
|
-
const STATE_SIGNATURE_DOMAIN = "effect-agent-pr-review/state-
|
|
1890
|
+
const STATE_MARKER_PATTERN = /(?:^|\n)<!-- effect-agent-pr-review state-v2:([A-Za-z0-9+/]+={0,2})\.([0-9a-f]{64}) -->$/;
|
|
1891
|
+
const STATE_SIGNATURE_DOMAIN = "effect-agent-pr-review/state-v2\0";
|
|
1469
1892
|
const MAX_REVIEW_STATE_MARKER_CHARS = 24e3;
|
|
1470
|
-
const ReviewStateMarker = Schema.NonEmptyString.check(Schema.isMaxLength(MAX_REVIEW_STATE_MARKER_CHARS), Schema.isPattern(/^<!-- effect-agent-pr-review state-
|
|
1893
|
+
const ReviewStateMarker = Schema.NonEmptyString.check(Schema.isMaxLength(MAX_REVIEW_STATE_MARKER_CHARS), Schema.isPattern(/^<!-- effect-agent-pr-review state-v2:[A-Za-z0-9+/]+={0,2}\.[0-9a-f]{64} -->$/)).pipe(Schema.brand("@effect-agent/pr-review/ReviewStateMarker"));
|
|
1471
1894
|
var ReviewStateAuthenticationFailure = class extends Schema.TaggedError()("ReviewStateAuthenticationFailure", {
|
|
1472
1895
|
operation: Schema.Literals(["sign", "verify"]),
|
|
1473
1896
|
reason: Schema.NonEmptyString.check(Schema.isMaxLength(2048))
|
|
@@ -1571,11 +1994,15 @@ const fullSelection = (input) => ({
|
|
|
1571
1994
|
reason: input.reason,
|
|
1572
1995
|
files: input.files,
|
|
1573
1996
|
affectedPaths: input.files.flatMap((file) => file.previousPath === void 0 ? [file.path] : [file.path, file.previousPath]),
|
|
1997
|
+
retryPaths: [],
|
|
1998
|
+
retryStages: [],
|
|
1574
1999
|
totalFiles: input.totalFiles,
|
|
1575
2000
|
baselineSha: void 0,
|
|
1576
2001
|
priorState: void 0,
|
|
1577
2002
|
profileFingerprint: input.profileFingerprint
|
|
1578
2003
|
});
|
|
2004
|
+
/** Three-dot lineage from the reviewed head to the current head is usable. */
|
|
2005
|
+
const isLineageAncestor = (comparison, priorState, currentHeadSha) => comparison.baseSha === priorState.reviewedHeadSha && comparison.headSha === currentHeadSha && comparison.mergeBaseSha === priorState.reviewedHeadSha && !comparison.truncated && (comparison.status === "ahead" || comparison.status === "identical");
|
|
1579
2006
|
/**
|
|
1580
2007
|
* Validate that persisted state belongs to this exact PR/base lineage and the
|
|
1581
2008
|
* same review profile. A mismatch is a full-review reason, never an error that
|
|
@@ -1588,6 +2015,52 @@ const validateReviewState = (state, current, profileFingerprint) => {
|
|
|
1588
2015
|
if (state.headRef !== current.headRef) return "the pull request head ref changed";
|
|
1589
2016
|
if (state.profileFingerprint !== profileFingerprint) return "the reviewer profile or model configuration changed";
|
|
1590
2017
|
};
|
|
2018
|
+
const filePaths = (file) => file.previousPath === void 0 ? [file.path] : [file.path, file.previousPath];
|
|
2019
|
+
const incrementalFromDelta = (input) => {
|
|
2020
|
+
const currentPaths = new Set(input.fullFiles.flatMap(filePaths));
|
|
2021
|
+
const affectedPaths = /* @__PURE__ */ new Set([...input.deltaFiles.flatMap(filePaths), ...input.extraAffectedPaths ?? []]);
|
|
2022
|
+
const selectedByPath = /* @__PURE__ */ new Map();
|
|
2023
|
+
for (const file of input.deltaFiles) if (currentPaths.has(file.path) || file.previousPath !== void 0 && currentPaths.has(file.previousPath)) selectedByPath.set(file.path, file);
|
|
2024
|
+
const carriedPaths = input.priorState.unreviewedPaths.filter((path) => currentPaths.has(path));
|
|
2025
|
+
const surgical = input.priorState.unreviewedPasses !== void 0;
|
|
2026
|
+
const retryOnly = /* @__PURE__ */ new Set();
|
|
2027
|
+
const retryStages = /* @__PURE__ */ new Set();
|
|
2028
|
+
for (const path of carriedPaths) {
|
|
2029
|
+
if (affectedPaths.has(path)) continue;
|
|
2030
|
+
retryOnly.add(path);
|
|
2031
|
+
if (surgical) {
|
|
2032
|
+
for (const pass of input.priorState.unreviewedPasses ?? []) if (pass.paths.includes(path)) retryStages.add(pass.stage);
|
|
2033
|
+
} else affectedPaths.add(path);
|
|
2034
|
+
}
|
|
2035
|
+
if (surgical && retryStages.has("verification") && !retryStages.has("discovery") && !retryStages.has("specialist")) {
|
|
2036
|
+
retryStages.add("discovery");
|
|
2037
|
+
retryStages.add("specialist");
|
|
2038
|
+
}
|
|
2039
|
+
if (surgical && retryOnly.size > 0 && retryStages.size === 0) {
|
|
2040
|
+
for (const path of retryOnly) affectedPaths.add(path);
|
|
2041
|
+
retryStages.add("discovery");
|
|
2042
|
+
retryStages.add("specialist");
|
|
2043
|
+
retryStages.add("verification");
|
|
2044
|
+
}
|
|
2045
|
+
if (carriedPaths.length > 0 || (input.extraAffectedPaths?.length ?? 0) > 0) {
|
|
2046
|
+
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);
|
|
2047
|
+
}
|
|
2048
|
+
const selectedFiles = [...selectedByPath.values()].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
2049
|
+
const leftoverCount = [...retryOnly].filter((path) => !affectedPaths.has(path)).length;
|
|
2050
|
+
const carriedReason = leftoverCount > 0 && surgical ? `; retrying ${leftoverCount} unchanged leftover path(s) without rediscovery` : carriedPaths.length > 0 ? `; retrying ${carriedPaths.length} carried unreviewed path(s)` : "";
|
|
2051
|
+
return {
|
|
2052
|
+
mode: "incremental",
|
|
2053
|
+
reason: `${input.reason}${carriedReason}`,
|
|
2054
|
+
files: selectedFiles,
|
|
2055
|
+
affectedPaths: [...affectedPaths].sort(),
|
|
2056
|
+
retryPaths: [...retryOnly].filter((path) => !affectedPaths.has(path)).sort(),
|
|
2057
|
+
retryStages: [...retryStages].sort(),
|
|
2058
|
+
totalFiles: selectedFiles.length,
|
|
2059
|
+
baselineSha: input.priorState.reviewedHeadSha,
|
|
2060
|
+
priorState: input.priorState,
|
|
2061
|
+
profileFingerprint: input.profileFingerprint
|
|
2062
|
+
};
|
|
2063
|
+
};
|
|
1591
2064
|
/** Pure, deterministic range selection with conservative full-review fallbacks. */
|
|
1592
2065
|
const selectReviewRange = (input) => {
|
|
1593
2066
|
const full = (reason) => fullSelection({
|
|
@@ -1602,38 +2075,38 @@ const selectReviewRange = (input) => {
|
|
|
1602
2075
|
const invalid = validateReviewState(input.priorState, input.current, input.profileFingerprint);
|
|
1603
2076
|
if (invalid !== void 0) return full(invalid);
|
|
1604
2077
|
const comparison = input.comparison;
|
|
1605
|
-
if (comparison
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
for (const file of baseComparison.files) {
|
|
1615
|
-
affectedPaths.add(file.path);
|
|
1616
|
-
if (file.previousPath !== void 0) affectedPaths.add(file.previousPath);
|
|
2078
|
+
if (comparison !== void 0 && isLineageAncestor(comparison, input.priorState, input.current.headSha)) {
|
|
2079
|
+
const extraAffected = [];
|
|
2080
|
+
let baseReason = "";
|
|
2081
|
+
if (input.priorState.baseSha !== input.current.baseSha) {
|
|
2082
|
+
const baseComparison = input.baseComparison;
|
|
2083
|
+
if (baseComparison === void 0) return full("the pull request base changed and its lineage comparison was unavailable");
|
|
2084
|
+
if (baseComparison.baseSha !== input.priorState.baseSha || baseComparison.headSha !== input.current.baseSha || baseComparison.mergeBaseSha !== input.priorState.baseSha || baseComparison.status !== "ahead" && baseComparison.status !== "identical" || baseComparison.truncated) return full("the pull request base changed materially or exceeded the comparison bound");
|
|
2085
|
+
for (const file of baseComparison.files) extraAffected.push(...filePaths(file));
|
|
2086
|
+
baseReason = `; base advanced from ${input.priorState.baseSha.slice(0, 7)} and overlapping PR paths were included`;
|
|
1617
2087
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
2088
|
+
return incrementalFromDelta({
|
|
2089
|
+
current: input.current,
|
|
2090
|
+
fullFiles: input.fullFiles,
|
|
2091
|
+
profileFingerprint: input.profileFingerprint,
|
|
2092
|
+
priorState: input.priorState,
|
|
2093
|
+
deltaFiles: comparison.files,
|
|
2094
|
+
extraAffectedPaths: extraAffected,
|
|
2095
|
+
reason: `changes since reviewed head ${input.priorState.reviewedHeadSha.slice(0, 7)}${baseReason}`
|
|
2096
|
+
});
|
|
1625
2097
|
}
|
|
1626
|
-
const
|
|
1627
|
-
return {
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
affectedPaths: [...affectedPaths].sort(),
|
|
1632
|
-
totalFiles: selectedFiles.length,
|
|
1633
|
-
baselineSha: input.priorState.reviewedHeadSha,
|
|
2098
|
+
const contentComparison = input.contentComparison;
|
|
2099
|
+
if (contentComparison !== void 0 && !contentComparison.truncated) return incrementalFromDelta({
|
|
2100
|
+
current: input.current,
|
|
2101
|
+
fullFiles: input.fullFiles,
|
|
2102
|
+
profileFingerprint: input.profileFingerprint,
|
|
1634
2103
|
priorState: input.priorState,
|
|
1635
|
-
|
|
1636
|
-
|
|
2104
|
+
deltaFiles: contentComparison.files,
|
|
2105
|
+
reason: `rewritten history; contents changed since reviewed head ${input.priorState.reviewedHeadSha.slice(0, 7)}`
|
|
2106
|
+
});
|
|
2107
|
+
if (comparison === void 0) return full("the incremental head comparison was unavailable");
|
|
2108
|
+
if (comparison.truncated) return full("the incremental comparison exceeded GitHub's file bound");
|
|
2109
|
+
return full("the prior reviewed head is not an ancestor of the current head");
|
|
1637
2110
|
};
|
|
1638
2111
|
/** Per-run context consumed by orchestration and publication, not by the model. */
|
|
1639
2112
|
var ReviewExecutionContext = class extends Context.Service()("@effect-agent/pr-review/ReviewExecutionContext") {};
|
|
@@ -1667,11 +2140,6 @@ const selectedPullRequestSourceLayer = (selection) => Layer.effect(PullRequestSo
|
|
|
1667
2140
|
}))
|
|
1668
2141
|
});
|
|
1669
2142
|
}));
|
|
1670
|
-
/** Profile fingerprints are SHA-256 over configuration-only signatures. */
|
|
1671
|
-
const computeProfileFingerprint = (signature) => Effect.promise(async () => {
|
|
1672
|
-
const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(signature));
|
|
1673
|
-
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1674
|
-
});
|
|
1675
2143
|
/** Build the full-surface mission used only to resolve profile guidance. */
|
|
1676
2144
|
const buildProfileMission = (metadata, files) => ReviewMission.make({
|
|
1677
2145
|
repository: metadata.repository,
|
|
@@ -1725,7 +2193,7 @@ var ReviewRetirementReport = class extends Schema.Class("@effect-agent/pr-review
|
|
|
1725
2193
|
const findingIdentity = (finding) => `${finding.path}\u0000${finding.startLine}\u0000${finding.endLine}\u0000${finding.title}`;
|
|
1726
2194
|
const REVIEW_METADATA_PATTERN = /<!-- effect-agent-pr-review metadata\n[\s\S]*?\n-->/g;
|
|
1727
2195
|
const FINGERPRINT_PATTERN = /<!-- effect-agent-pr-review fingerprint=sha256:[0-9a-f]{64} -->/g;
|
|
1728
|
-
const STATE_PATTERN = /<!-- effect-agent-pr-review state-
|
|
2196
|
+
const STATE_PATTERN = /<!-- effect-agent-pr-review state-v\d+:[A-Za-z0-9+/]+={0,2}\.[0-9a-f]{64} -->/g;
|
|
1729
2197
|
const RETIRED_ORIGINAL_PATTERN = /<!-- effect-agent-pr-review retired-original:start -->\n([\s\S]*?)\n<!-- effect-agent-pr-review retired-original:end -->/;
|
|
1730
2198
|
const MACHINE_COMMENT_PATTERN = new RegExp(`${REVIEW_METADATA_PATTERN.source}|${FINGERPRINT_PATTERN.source}|${STATE_PATTERN.source}`, "g");
|
|
1731
2199
|
const VERDICT_CALLOUT_PATTERN = /^(?:> \[!(?:CAUTION|IMPORTANT)\]\n> [^\n]*(?:\n> [^\n]*)*|> (?:ℹ️|✅)[^\n]*)\n*/;
|
|
@@ -1934,8 +2402,8 @@ const GitHubReviewCommentWire = Schema.Struct({
|
|
|
1934
2402
|
node_id: Schema.String,
|
|
1935
2403
|
path: Schema.String,
|
|
1936
2404
|
body: Schema.String,
|
|
1937
|
-
line: Schema.NullOr(Schema.Int),
|
|
1938
|
-
original_line: Schema.NullOr(Schema.Int),
|
|
2405
|
+
line: Schema.optionalKey(Schema.NullOr(Schema.Int)),
|
|
2406
|
+
original_line: Schema.optionalKey(Schema.NullOr(Schema.Int)),
|
|
1939
2407
|
start_line: Schema.optionalKey(Schema.NullOr(Schema.Int)),
|
|
1940
2408
|
original_start_line: Schema.optionalKey(Schema.NullOr(Schema.Int))
|
|
1941
2409
|
});
|
|
@@ -2176,8 +2644,9 @@ const gitHubReviewRetirementHostLayer = Layer.effect(ReviewRetirementHost)(Effec
|
|
|
2176
2644
|
url: `${prefix}/reviews/${reviewId}/comments`,
|
|
2177
2645
|
decode: decodeRetirement(GitHubReviewCommentsPageWire, "listReviewCommentsForRetirement")
|
|
2178
2646
|
}).pipe(Effect.map((comments) => comments.map((comment) => {
|
|
2179
|
-
const
|
|
2180
|
-
const
|
|
2647
|
+
const positiveLine = (value) => value !== void 0 && value !== null && value > 0 ? value : null;
|
|
2648
|
+
const endLine = positiveLine(comment.line ?? comment.original_line);
|
|
2649
|
+
const startLine = positiveLine(comment.start_line ?? comment.original_start_line) ?? endLine;
|
|
2181
2650
|
return RetirableReviewComment.make({
|
|
2182
2651
|
nodeId: comment.node_id,
|
|
2183
2652
|
path: comment.path,
|
|
@@ -2267,8 +2736,8 @@ const gitHubPriorReviewsLayer = Layer.effect(PriorReviews)(Effect.gen(function*
|
|
|
2267
2736
|
latestState
|
|
2268
2737
|
};
|
|
2269
2738
|
}).pipe(Effect.provideService(HttpClient.HttpClient, client));
|
|
2270
|
-
const
|
|
2271
|
-
const wire = yield* (yield* HttpClient.execute(withCommonHeaders(HttpClientRequest.get(`${target.apiUrl}/repos/${target.repository}/compare/${encodeURIComponent(baseSha)}
|
|
2739
|
+
const compareCommits = (baseSha, headSha, separator) => Effect.gen(function* () {
|
|
2740
|
+
const wire = yield* (yield* HttpClient.execute(withCommonHeaders(HttpClientRequest.get(`${target.apiUrl}/repos/${target.repository}/compare/${encodeURIComponent(baseSha)}${separator}${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))));
|
|
2272
2741
|
const files = wire.files.map(toChangedFile);
|
|
2273
2742
|
return ReviewHeadComparison.make({
|
|
2274
2743
|
status: wire.status,
|
|
@@ -2279,13 +2748,22 @@ const gitHubPriorReviewsLayer = Layer.effect(PriorReviews)(Effect.gen(function*
|
|
|
2279
2748
|
truncated: files.length >= 300
|
|
2280
2749
|
});
|
|
2281
2750
|
}).pipe(Effect.provideService(HttpClient.HttpClient, client));
|
|
2751
|
+
const compareTrees = (baseSha, headSha) => compareCommits(baseSha, headSha, "..").pipe(Effect.map((comparison) => ReviewHeadComparison.make({
|
|
2752
|
+
status: comparison.status === "identical" ? "identical" : "ahead",
|
|
2753
|
+
baseSha,
|
|
2754
|
+
headSha,
|
|
2755
|
+
mergeBaseSha: baseSha,
|
|
2756
|
+
files: comparison.files,
|
|
2757
|
+
truncated: comparison.truncated
|
|
2758
|
+
})));
|
|
2282
2759
|
return PriorReviews.of({
|
|
2283
2760
|
latestFingerprint: readMarkers(Option.none()).pipe(Effect.map((markers) => markers.latestFingerprint)),
|
|
2284
2761
|
latestState: Effect.gen(function* () {
|
|
2285
2762
|
const authenticator = yield* ReviewStateAuthenticator;
|
|
2286
2763
|
return yield* readMarkers(Option.some(authenticator)).pipe(Effect.map((markers) => markers.latestState));
|
|
2287
2764
|
}),
|
|
2288
|
-
compareHeads
|
|
2765
|
+
compareHeads: (baseSha, headSha) => compareCommits(baseSha, headSha, "..."),
|
|
2766
|
+
compareTrees
|
|
2289
2767
|
});
|
|
2290
2768
|
}));
|
|
2291
2769
|
/**
|
|
@@ -2298,6 +2776,6 @@ const fingerprintUnchanged = (current) => Effect.gen(function* () {
|
|
|
2298
2776
|
return Option.isSome(latest) && latest.value === current;
|
|
2299
2777
|
});
|
|
2300
2778
|
//#endregion
|
|
2301
|
-
export {
|
|
2779
|
+
export { extractFingerprint as $, ChangedFileStatus as $n, ReviewAssurance as $t, ReviewState as A, ReviewToolkit as An, MAX_MERGED_FINDINGS as At, fromStoredConcern as B, readFileDiffHandler as Bn, ReviewRiskCategory as Bt, MAX_REVIEW_STATE_MARKER_CHARS as C, PullRequestReviewer as Cn, defaultFileReviewerPolicy as Ct, ReviewHeadComparison as D, ReviewConcern as Dn, reviewCandidateSubjectKey as Dt, ReviewExecutionContext as E, ReadFileDiff as En, makeFileReviewerInstructions as Et, StoredReviewConcern as F, defaultReviewPolicy as Fn, ReviewDiscoveryPass as Ft, toStoredConcern as G, MAX_FILE_CHARS as Gn, UNIT_EVIDENCE_CHAR_BUDGET as Gt, isLineageAncestor as H, resolveGuidance as Hn, ReviewUnitId as Ht, StoredReviewFinding as I, fileDiffView as In, ReviewDiscoveryPerspective as It, validateReviewState as J, PullRequestSourceFailure as Jn, planReviewUnits as Jt, toStoredFinding as K, PullRequestMetadata as Kn, classifyReviewRisks as Kt, StoredUnreviewedPass as L, fileReviewEvidenceChunks as Ln, ReviewEvidenceShard as Lt, ReviewStateAuthenticator as M, ReviewVerdict as Mn, MAX_REVIEW_UNITS as Mt, ReviewStateMarker as N, WalkthroughEntry as Nn, MAX_UNIT_EVIDENCE_SHARDS as Nt, ReviewMode as O, ReviewFinding as On, runFanOutReview as Ot, ReviewStateMarkerTooLarge as P, clampMaxFindings as Pn, MAX_UNIT_FILES as Pt, computeProfileFingerprint as Q, ChangedFile as Qn, FailedReviewUnit as Qt, UnreviewedStage as R, listChangedFilesHandler as Rn, ReviewEvidenceShardId as Rt, GitCommitSha as S, MAX_WALKTHROUGH_SUMMARY_CHARS as Sn, confirmedFindingForPublication as St, MAX_STORED_UNREVIEWED_PATHS as T, ReadFile as Tn, makeFileReviewerDefinition as Tt, selectReviewRange as U, reviewInstructions as Un, ReviewUnitPlan as Ut, fromStoredFinding as V, readFileHandler as Vn, ReviewUnit as Vt, selectedPullRequestSourceLayer as W, MAX_CHANGED_FILES as Wn, UNIT_CHANGED_LINE_BUDGET as Wt, FINGERPRINT_MARKER_LENGTH as X, normalizeRepoRelativePath as Xn, rankAndDedupeFindings as Xt, webCryptoReviewStateAuthenticatorLayer as Y, ReviewInputViolation as Yn, rankAndDedupeConcerns as Yt, computeChangesetFingerprint as Z, anchorViolation as Zn, FailedReviewPass as Zt, ReviewRetirementHost as _, ListChangedFilesQuery as _n, ReviewCandidateId as _t, PriorReviews as a, fanOutInputCoverage as an, isReviewableFile as ar, FileReviewEvidence as at, hasReviewMetadataMarker as b, MAX_PATCH_CHARS as bn, ReviewWorkPhase as bt, fingerprintUnchanged as c, ChangedFilesView as cn, FileReviewer as ct, gitHubReviewPublisherLayer as d, FileDiffView as dn, MAX_CHILD_FINDINGS as dt, ReviewCoverage as en, ChangedPath as er, renderFingerprintMarker as et, gitHubReviewRetirementHostLayer as f, FileSlice as fn, MAX_FILE_REVIEW_TOOL_CALLS as ft, ReviewRetirementFailure as g, ListChangedFiles as gn, ReviewCandidate as gt, RetirableReviewComment as h, FindingSeverity as hn, REVIEW_UNIT_CONCURRENCY as ht, PriorReviewLookupFailure as i, compatibilityCoverage as in, hasReviewableContent as ir, FileReviewBrief as it, ReviewStateAuthenticationFailure as j, ReviewToolkitLayer as jn, MAX_REPORTED_UNASSIGNED_EVIDENCE_SHARDS as jt, ReviewScopeMode as k, ReviewMission as kn, MAX_FILE_EVIDENCE_CHARS as kt, gitHubPriorReviewsLayer as l, CodeReview as ln, FindingCandidate as lt, RetirableReview as m, FindingCategory as mn, MAX_UNIT_CANDIDATES as mt, GitHubApiFailure as n, assessFlatReview as nn, annotatePatch as nr, ConcernCandidate as nt, PublishedReview as o, flatAssurance as on, parsePatch as or, FileReviewReport as ot, parseGitHubSubmittedAt as p, FileSliceQuery as pn, MAX_REVIEW_CHILDREN as pt, unavailableReviewStateAuthenticatorLayer as q, PullRequestSource as qn, findingAnchorInUnitEvidence as qt, GitHubReviewTarget as r, boundedListReason as rn, commentableLines as rr, DiscoveredConcern as rt, ReviewPublisher as s, ChangedFileSummary as sn, renderReviewContent as sr, FileReviewToolkit as st, DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN as t, ReviewInputCoverage as tn, MAX_REVIEW_CONTENT_CHARS as tr, CandidateAssessment as tt, gitHubPullRequestSourceLayer as u, FileDiffQuery as un, MAX_CHILD_CONCERNS as ut, ReviewRetirementReport as v, MAX_CONCERNS as vn, ReviewPassMisbehaved as vt, MAX_STORED_UNREVIEWED_PASSES as w, REVIEW_TOOL_RESULT_MAX_BYTES as wn, fileReviewerInstructions as wt, retireStaleReviews as x, MAX_WALKTHROUGH_ENTRIES as xn, assessmentSettlesSuggestionExactly as xt, decideReviewRetirement as y, MAX_FINDINGS as yn, ReviewWorkPerspective as yt, buildProfileMission as z, makeReviewInstructions as zn, ReviewPassId as zt };
|
|
2302
2780
|
|
|
2303
|
-
//# sourceMappingURL=github-
|
|
2781
|
+
//# sourceMappingURL=github-C6jrBLA2.mjs.map
|