@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.
@@ -1,5 +1,5 @@
1
- import { Context, DateTime, Effect, Encoding, Layer, Option, Redacted, Result, Schema } from "effect";
2
- import { Agent, AgentPolicy, Subagent, SubagentPolicy, SubagentRuntime, ToolExecutionClass, ToolResultBounds } from "effect-agent";
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
- /** General + specialist discovery for every unit, then one verifier per unit. */
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. Enforced identically by the live delegation projection
982
- * and the independent host coverage fold.
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
- /** Bounded coordinator-visible result with host-assigned candidate IDs. */
1059
- var FileReviewUnitResult = class extends Schema.Class("@effect-agent/pr-review/FileReviewUnitResult")({
1060
- phase: ReviewWorkPhase,
1061
- workId: ReviewPassId,
1062
- unitId: ReviewUnitId,
1063
- candidates: Candidates,
1064
- fileSummaries: Schema.Array(WalkthroughEntry).check(Schema.isMaxLength(12)),
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 fileReviewPolicy = SubagentPolicy.make({
1127
- maxChildren: MAX_REVIEW_CHILDREN,
1128
- maxConcurrency: 4,
1129
- maxTurns: 6,
1130
- maxToolCalls: 1,
1131
- maxDuration: "6 minutes",
1132
- maxResultBytes: 256 * 1024
1133
- });
1134
- const mapFileReviewChildFailure = (failure) => FileReviewUnitFailed.make({
1135
- childErrorTag: failure._tag,
1136
- message: (failure.message ?? "").slice(0, 400)
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
- if (file === void 0) return yield* rejectWork(request.workId, `planned evidence path is unavailable: ${shard.path}`);
1171
- const chunks = fileReviewEvidenceChunks(file);
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 FileReviewBrief.make({
1185
- ...request,
1186
- evidence
1187
- });
1380
+ return evidence;
1188
1381
  });
1189
- const candidateOrdinal = (index) => String(index + 1).padStart(3, "0");
1190
- const projectReviewResult = (report, context, request) => {
1191
- if (context.budgetExhausted) return Effect.fail(rejectWork(report.workId, "review work exhausted its budget before exact settlement"));
1192
- if (report.phase !== request.phase || report.workId !== request.workId || report.unitId !== request.unitId) return Effect.fail(rejectWork(request.workId, "review output identity does not match the scheduled request"));
1193
- if (report.phase === "verification") {
1194
- if (report.findings.length > 0 || report.concerns.length > 0 || report.fileSummaries.length > 0) return Effect.fail(rejectWork(report.workId, "verification output contained discovery-only fields"));
1195
- const expectedById = new Map(request.candidates.map((candidate) => [candidate.candidateId, candidate]));
1196
- const assessedIds = /* @__PURE__ */ new Set();
1197
- for (const assessment of report.assessments) {
1198
- const candidate = expectedById.get(assessment.candidateId);
1199
- if (candidate === void 0 || assessedIds.has(assessment.candidateId)) return Effect.fail(rejectWork(report.workId, "verification output did not assess the exact candidate set"));
1200
- if (!assessmentSettlesSuggestionExactly(assessment, candidate)) return Effect.fail(rejectWork(report.workId, "verification output did not settle suggestion publication exactly"));
1201
- assessedIds.add(assessment.candidateId);
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
- if (assessedIds.size !== expectedById.size) return Effect.fail(rejectWork(report.workId, "verification output did not assess the exact candidate set"));
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
- if (report.assessments.length > 0) return Effect.fail(rejectWork(report.workId, "discovery output contained verification-only assessments"));
1214
- const allowed = new Set(request.paths);
1215
- if (report.findings.some((finding) => !allowed.has(finding.path)) || report.concerns.some((candidate) => candidate.evidencePaths.some((path) => !allowed.has(path))) || report.fileSummaries.some((entry) => !allowed.has(entry.path))) return Effect.fail(rejectWork(report.workId, "discovery output referenced evidence outside the scheduled unit"));
1216
- return Effect.gen(function* () {
1217
- const source = yield* PullRequestSource;
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
- const findingCandidates = report.findings.map((finding, index) => FindingCandidate.make({
1229
- candidateId: `${request.workId}:finding:${candidateOrdinal(index)}`,
1230
- workId: request.workId,
1231
- unitId: request.unitId,
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
- const concernCandidates = report.concerns.map((candidate, index) => ConcernCandidate.make({
1236
- candidateId: `${request.workId}:concern:${candidateOrdinal(index)}`,
1237
- workId: request.workId,
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
- return FileReviewUnitResult.make({
1243
- phase: report.phase,
1244
- workId: report.workId,
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 delegationDescription = "Run exactly one host-planned discovery or candidate-verification child. Copy every plan field and candidate verbatim; never retry failed work.";
1253
- const makeFileReviewDelegation = (child) => Subagent.define("delegate_file_review", {
1254
- description: delegationDescription,
1255
- target: child,
1256
- parameters: FileReviewRequest,
1257
- success: FileReviewUnitResult,
1258
- failure: FileReviewFailure,
1259
- failureMode: "return",
1260
- prepareInput: prepareReviewBrief,
1261
- projectResult: projectReviewResult,
1262
- policy: fileReviewPolicy
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
- var ListReviewUnitsQuery = class extends Schema.Class("@effect-agent/pr-review/ListReviewUnitsQuery")({ scope: Schema.Literal("all") }) {};
1265
- const ListReviewUnits = Tool.make("list_review_units", {
1266
- description: "List deterministic bounded review units, explicit risk categories, every required discovery pass, and paths the pipeline cannot cover.",
1267
- parameters: ListReviewUnitsQuery,
1268
- success: ReviewUnitPlan,
1269
- failure: PullRequestSourceFailure,
1270
- failureMode: "error",
1271
- dependencies: [PullRequestSource]
1272
- }).annotate(ToolExecutionClass, "readonly");
1273
- const FanOutCoordinatorToolkit = Toolkit.make(ListReviewUnits);
1274
- const FanOutCoordinatorToolkitLayer = FanOutCoordinatorToolkit.toLayer({ list_review_units: () => Effect.gen(function* () {
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 fanOutReviewInstructions = makeFanOutReviewInstructions();
1293
- const defaultFanOutPolicy = AgentPolicy.make({
1294
- maxTurns: 7,
1295
- maxToolCalls: 25,
1296
- maxDuration: "20 minutes",
1297
- toolConcurrency: 4,
1298
- repeatedFailureLimit: 3,
1299
- tokenBudget: 4e5,
1300
- contextTokenLimit: 15e4,
1301
- onExhaustion: "final-answer"
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
- const delegationToolFor = (delegation) => delegation.tool.annotate(ToolExecutionClass, "readonly");
1317
- const makeFanOutReviewerDefinition = (options, delegation) => Agent.define("pr-fanout-reviewer", {
1318
- input: ReviewMission,
1319
- output: CodeReview,
1320
- instructions: makeFanOutReviewInstructions(options),
1321
- toolkit: Toolkit.make(ListReviewUnits, delegationToolFor(delegation)),
1322
- policy: defaultFanOutPolicy,
1323
- description: "Coordinate deterministic general/specialist discovery and independent candidate verification over bounded review units.",
1324
- metadata: {
1325
- deploymentClass: "E",
1326
- surface: "read-only",
1327
- delegation: "S1-attached",
1328
- assurance: "multi-pass"
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
- child,
1336
- parent: makeFanOutReviewerDefinition(options, delegation),
1337
- delegation
1670
+ plan,
1671
+ passesByUnit,
1672
+ overflowRetryPaths
1338
1673
  };
1339
1674
  };
1340
- const defaultSuite = makeFanOutReviewSuite();
1341
- const FileReviewer = defaultSuite.child;
1342
- const FanOutReviewer = defaultSuite.parent;
1343
- const fileReviewDelegation = defaultSuite.delegation;
1344
- const DelegateFileReview = delegationToolFor(fileReviewDelegation);
1345
- const FanOutReviewToolkit = FanOutReviewer.toolkit;
1346
- const FileReviewDelegationFailure = fileReviewDelegation.containedFailure;
1347
- const fanOutHandlersLayerFor = (delegation) => (childBinding) => SubagentRuntime.layer(delegation, childBinding, { mapChildFailure: mapFileReviewChildFailure });
1348
- const fanOutHandlersLayer = fanOutHandlersLayerFor(fileReviewDelegation);
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
- /** WebCrypto SHA-256; unavailable crypto is a defect, not an expected failure. */
1365
- const sha256Hex = (text) => Effect.promise(async () => {
1366
- const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
1367
- return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
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 only after complete input assignment and settled
1415
- * configured review assurance. The head plus full-scope fingerprint forms an
1416
- * incremental baseline; an absent unresolved item never means the path is
1417
- * defect-free. The `acceptedScopeFingerprint` name is retained for wire
1418
- * compatibility. Storing hundreds of path strings separately would not fit
1419
- * GitHub's bounded review body in the worst case.
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(1),
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-v1:";
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-v1:([A-Za-z0-9+/]+={0,2})\.([0-9a-f]{64}) -->$/;
1468
- const STATE_SIGNATURE_DOMAIN = "effect-agent-pr-review/state-v1\0";
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-v1:[A-Za-z0-9+/]+={0,2}\.[0-9a-f]{64} -->$/)).pipe(Schema.brand("@effect-agent/pr-review/ReviewStateMarker"));
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 === void 0) return full("the incremental head comparison was unavailable");
1606
- if (comparison.baseSha !== input.priorState.reviewedHeadSha || comparison.headSha !== input.current.headSha || comparison.mergeBaseSha !== input.priorState.reviewedHeadSha || comparison.status !== "ahead" && comparison.status !== "identical") return full("the prior reviewed head is not an ancestor of the current head");
1607
- if (comparison.truncated) return full("the incremental comparison exceeded GitHub's file bound");
1608
- const affectedPaths = new Set(comparison.files.flatMap((file) => file.previousPath === void 0 ? [file.path] : [file.path, file.previousPath]));
1609
- let baseReason = "";
1610
- if (input.priorState.baseSha !== input.current.baseSha) {
1611
- const baseComparison = input.baseComparison;
1612
- if (baseComparison === void 0) return full("the pull request base changed and its lineage comparison was unavailable");
1613
- 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");
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
- baseReason = `; base advanced from ${input.priorState.baseSha.slice(0, 7)} and overlapping PR paths were included`;
1619
- }
1620
- const currentPaths = new Set(input.fullFiles.flatMap((file) => file.previousPath === void 0 ? [file.path] : [file.path, file.previousPath]));
1621
- const selectedByPath = /* @__PURE__ */ new Map();
1622
- for (const file of comparison.files) if (currentPaths.has(file.path) || file.previousPath !== void 0 && currentPaths.has(file.previousPath)) selectedByPath.set(file.path, file);
1623
- if (input.priorState.baseSha !== input.current.baseSha) {
1624
- for (const file of input.fullFiles) if (affectedPaths.has(file.path) || file.previousPath !== void 0 && affectedPaths.has(file.previousPath)) selectedByPath.set(file.path, file);
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 selectedFiles = [...selectedByPath.values()].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
1627
- return {
1628
- mode: "incremental",
1629
- reason: `changes since settled review head ${input.priorState.reviewedHeadSha.slice(0, 7)}${baseReason}`,
1630
- files: selectedFiles,
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
- profileFingerprint: input.profileFingerprint
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-v1:[A-Za-z0-9+/]+={0,2}\.[0-9a-f]{64} -->/g;
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 endLine = comment.line ?? comment.original_line;
2180
- const startLine = comment.start_line ?? comment.original_start_line ?? endLine;
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 compareHeads = (baseSha, headSha) => Effect.gen(function* () {
2271
- const wire = yield* (yield* HttpClient.execute(withCommonHeaders(HttpClientRequest.get(`${target.apiUrl}/repos/${target.repository}/compare/${encodeURIComponent(baseSha)}...${encodeURIComponent(headSha)}`).pipe(HttpClientRequest.acceptJson), target.token)).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.mapError(asLookupFailure))).json.pipe(Effect.mapError(asLookupFailure), Effect.flatMap((body) => Schema.decodeUnknownEffect(GitHubCompareWire)(body).pipe(Effect.mapError(asLookupFailure))));
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 { DiscoveredConcern as $, normalizeRepoRelativePath as $n, ReviewPassId as $t, ReviewStateAuthenticator as A, ReviewConcern as An, defaultFanOutPolicy as At, selectedPullRequestSourceLayer as B, fileReviewEvidenceChunks as Bn, makeFileReviewerInstructions as Bt, MAX_REVIEW_STATE_MARKER_CHARS as C, MAX_PATCH_CHARS as Cn, MAX_UNIT_CANDIDATES as Ct, ReviewScopeMode as D, REVIEW_TOOL_RESULT_MAX_BYTES as Dn, ReviewWorkPhase as Dt, ReviewMode as E, PullRequestReviewer as En, ReviewWorkPerspective as Et, buildProfileMission as F, ReviewVerdict as Fn, fileReviewDelegation as Ft, webCryptoReviewStateAuthenticatorLayer as G, resolveGuidance as Gn, MAX_REPORTED_UNASSIGNED_EVIDENCE_SHARDS as Gt, toStoredFinding as H, makeReviewInstructions as Hn, reviewCandidateSubjectKey as Ht, computeProfileFingerprint as I, WalkthroughEntry as In, fileReviewPolicy as It, extractFingerprint as J, MAX_FILE_CHARS as Jn, MAX_UNIT_FILES as Jt, FINGERPRINT_MARKER_LENGTH as K, reviewInstructions as Kn, MAX_REVIEW_UNITS as Kt, fromStoredConcern as L, clampMaxFindings as Ln, fileReviewerInstructions as Lt, ReviewStateMarkerTooLarge as M, ReviewMission as Mn, fanOutHandlersLayer as Mt, StoredReviewConcern as N, ReviewToolkit as Nn, fanOutHandlersLayerFor as Nt, ReviewState as O, ReadFile as On, assessmentSettlesSuggestionExactly as Ot, StoredReviewFinding as P, ReviewToolkitLayer as Pn, fanOutReviewInstructions as Pt, DelegateFileReview as Q, ReviewInputViolation as Qn, ReviewEvidenceShardId as Qt, fromStoredFinding as R, defaultReviewPolicy as Rn, makeFanOutReviewInstructions as Rt, GitCommitSha as S, MAX_FINDINGS as Sn, MAX_REVIEW_CHILDREN as St, ReviewHeadComparison as T, MAX_WALKTHROUGH_SUMMARY_CHARS as Tn, ReviewCandidateId as Tt, unavailableReviewStateAuthenticatorLayer as U, readFileDiffHandler as Un, MAX_FILE_EVIDENCE_CHARS as Ut, toStoredConcern as V, listChangedFilesHandler as Vn, mapFileReviewChildFailure as Vt, validateReviewState as W, readFileHandler as Wn, MAX_MERGED_FINDINGS as Wt, CandidateAssessment as X, PullRequestSource as Xn, ReviewDiscoveryPerspective as Xt, renderFingerprintMarker as Y, PullRequestMetadata as Yn, ReviewDiscoveryPass as Yt, ConcernCandidate as Z, PullRequestSourceFailure as Zn, ReviewEvidenceShard as Zt, ReviewRetirementHost as _, FindingCategory as _n, ListReviewUnits as _t, PriorReviews as a, UNIT_EVIDENCE_CHAR_BUDGET as an, annotatePatch as ar, FileReviewDelegationFailure as at, hasReviewMetadataMarker as b, ListChangedFilesQuery as bn, MAX_CHILD_FINDINGS as bt, fingerprintUnchanged as c, planReviewUnits as cn, isReviewableFile as cr, FileReviewReport as ct, gitHubReviewPublisherLayer as d, ChangedFilesView as dn, FileReviewToolkitLayer as dt, ReviewRiskCategory as en, anchorViolation as er, FanOutCoordinatorToolkit as et, gitHubReviewRetirementHostLayer as f, CodeReview as fn, FileReviewUnitFailed as ft, ReviewRetirementFailure as g, FileSliceQuery as gn, FindingCandidate as gt, RetirableReviewComment as h, FileSlice as hn, FileReviewer as ht, PriorReviewLookupFailure as i, UNIT_CHANGED_LINE_BUDGET as in, MAX_REVIEW_CONTENT_CHARS as ir, FileReviewBrief as it, ReviewStateMarker as j, ReviewFinding as jn, defaultFileReviewerPolicy as jt, ReviewStateAuthenticationFailure as k, ReadFileDiff as kn, confirmedFindingForPublication as kt, gitHubPriorReviewsLayer as l, rankAndDedupeFindings as ln, parsePatch as lr, FileReviewRequest as lt, RetirableReview as m, FileDiffView as mn, FileReviewWorkRejected as mt, GitHubApiFailure as n, ReviewUnitId as nn, ChangedFileStatus as nr, FanOutReviewToolkit as nt, PublishedReview as o, classifyReviewRisks as on, commentableLines as or, FileReviewEvidence as ot, parseGitHubSubmittedAt as p, FileDiffQuery as pn, FileReviewUnitResult as pt, computeChangesetFingerprint as q, MAX_CHANGED_FILES as qn, MAX_UNIT_EVIDENCE_SHARDS as qt, GitHubReviewTarget as r, ReviewUnitPlan as rn, ChangedPath as rr, FanOutReviewer as rt, ReviewPublisher as s, findingAnchorInUnitEvidence as sn, hasReviewableContent as sr, FileReviewFailure as st, DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN as t, ReviewUnit as tn, ChangedFile as tr, FanOutCoordinatorToolkitLayer as tt, gitHubPullRequestSourceLayer as u, ChangedFileSummary as un, renderReviewContent as ur, FileReviewToolkit as ut, ReviewRetirementReport as v, FindingSeverity as vn, ListReviewUnitsQuery as vt, ReviewExecutionContext as w, MAX_WALKTHROUGH_ENTRIES as wn, ReviewCandidate as wt, retireStaleReviews as x, MAX_CONCERNS as xn, MAX_FILE_REVIEW_TOOL_CALLS as xt, decideReviewRetirement as y, ListChangedFiles as yn, MAX_CHILD_CONCERNS as yt, selectReviewRange as z, fileDiffView as zn, makeFanOutReviewSuite as zt };
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-BgtP7Rdv.mjs.map
2781
+ //# sourceMappingURL=github-C6jrBLA2.mjs.map