@effect-agent/pr-review 0.1.0-beta.21 → 0.1.0-beta.23

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
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";
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
@@ -483,7 +483,7 @@ var ReviewFinding = class extends Schema.Class("@effect-agent/pr-review/ReviewFi
483
483
  title: Schema.NonEmptyString.check(Schema.isMaxLength(120)),
484
484
  body: Schema.NonEmptyString.check(Schema.isMaxLength(2e3)),
485
485
  /** Replacement for exactly lines startLine..endLine; omit when unsure. */
486
- suggestion: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(2e3)))
486
+ suggestion: Schema.optionalKey(Schema.String.annotate({ description: "Committable replacement source code for exactly lines startLine..endLine: the full replacement for every line in the range and nothing else — never prose describing the change, which belongs in body." }).check(Schema.isMaxLength(2e3)))
487
487
  }) {};
488
488
  const ReviewVerdict = Schema.Literals([
489
489
  "approve",
@@ -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"]);
@@ -967,9 +1209,33 @@ const reviewCandidateSubjectKey = (candidate) => candidate._tag === "FindingCand
967
1209
  var CandidateAssessment = class extends Schema.Class("@effect-agent/pr-review/CandidateAssessment")({
968
1210
  candidateId: ReviewCandidateId,
969
1211
  disposition: Schema.Literals(["confirmed", "rejected"]),
1212
+ /**
1213
+ * Exact suggestion settlement: required when the candidate finding carries
1214
+ * a suggestion, forbidden otherwise. Untrusted child output cannot publish
1215
+ * a GitHub replacement block by prompt compliance alone — the host keeps a
1216
+ * confirmed finding's suggestion only on an exact "committable" settlement.
1217
+ */
1218
+ suggestion: Schema.optionalKey(Schema.Literals(["committable", "not-committable"]).annotate({ description: "Required exactly when the candidate finding carries a suggestion: \"committable\" only when its text is the full replacement source for exactly lines startLine..endLine and nothing else. Forbidden for candidates without a suggestion." })),
970
1219
  rationale: Schema.NonEmptyString.check(Schema.isMaxLength(600))
971
1220
  }) {};
972
1221
  /**
1222
+ * Exact suggestion settlement shape: a carried suggestion must be settled 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.
1225
+ */
1226
+ const assessmentSettlesSuggestionExactly = (assessment, candidate) => candidate._tag === "FindingCandidate" && candidate.finding.suggestion !== void 0 ? assessment.suggestion !== void 0 : assessment.suggestion === void 0;
1227
+ /**
1228
+ * Fail-closed publication of a confirmed finding: only an exact "committable"
1229
+ * settlement keeps the suggestion; anything else publishes the finding with
1230
+ * the suggestion stripped so unverified text can never become a one-click
1231
+ * GitHub replacement block.
1232
+ */
1233
+ const confirmedFindingForPublication = (assessment, candidate) => {
1234
+ if (candidate.finding.suggestion === void 0 || assessment.suggestion === "committable") return candidate.finding;
1235
+ const { suggestion: _stripped, ...finding } = candidate.finding;
1236
+ return ReviewFinding.make(finding);
1237
+ };
1238
+ /**
973
1239
  * Concern candidates need explicit paths internally to bind the claim to
974
1240
  * scheduled evidence. The verifier receives the complete bounded unit so it
975
1241
  * can use neighboring evidence to falsify the claim. The public ReviewConcern
@@ -983,18 +1249,6 @@ const UnitPaths = Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(S
983
1249
  const RiskCategories = Schema.Array(ReviewRiskCategory).check(Schema.isMaxLength(6));
984
1250
  const Candidates = Schema.Array(ReviewCandidate).check(Schema.isMaxLength(18));
985
1251
  const EvidenceShardIds = Schema.Array(ReviewEvidenceShardId).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12));
986
- /** Strict-object coordinator request for either discovery or verification. */
987
- var FileReviewRequest = class extends Schema.Class("@effect-agent/pr-review/FileReviewRequest")({
988
- phase: ReviewWorkPhase,
989
- workId: ReviewPassId,
990
- unitId: ReviewUnitId,
991
- paths: UnitPaths,
992
- evidenceShardIds: EvidenceShardIds,
993
- perspective: ReviewWorkPerspective,
994
- riskCategories: RiskCategories,
995
- /** Empty for discovery; the exact discovered set for unit verification. */
996
- candidates: Candidates
997
- }) {};
998
1252
  /** One complete host-selected evidence shard supplied to a review child. */
999
1253
  var FileReviewEvidence = class extends Schema.Class("@effect-agent/pr-review/FileReviewEvidence")({
1000
1254
  shardId: ReviewEvidenceShardId,
@@ -1018,6 +1272,7 @@ var FileReviewBrief = class extends Schema.Class("@effect-agent/pr-review/FileRe
1018
1272
  evidenceShardIds: EvidenceShardIds,
1019
1273
  perspective: ReviewWorkPerspective,
1020
1274
  riskCategories: RiskCategories,
1275
+ /** Empty for discovery; the exact discovered set for unit verification. */
1021
1276
  candidates: Candidates,
1022
1277
  evidence: Schema.Array(FileReviewEvidence).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12))
1023
1278
  }) {};
@@ -1031,24 +1286,16 @@ var FileReviewReport = class extends Schema.Class("@effect-agent/pr-review/FileR
1031
1286
  fileSummaries: Schema.Array(WalkthroughEntry).check(Schema.isMaxLength(12)),
1032
1287
  assessments: Schema.Array(CandidateAssessment).check(Schema.isMaxLength(18))
1033
1288
  }) {};
1034
- /** Bounded coordinator-visible result with host-assigned candidate IDs. */
1035
- var FileReviewUnitResult = class extends Schema.Class("@effect-agent/pr-review/FileReviewUnitResult")({
1036
- phase: ReviewWorkPhase,
1037
- workId: ReviewPassId,
1038
- unitId: ReviewUnitId,
1039
- candidates: Candidates,
1040
- fileSummaries: Schema.Array(WalkthroughEntry).check(Schema.isMaxLength(12)),
1041
- assessments: Schema.Array(CandidateAssessment).check(Schema.isMaxLength(18))
1042
- }) {};
1043
- var FileReviewUnitFailed = class extends Schema.TaggedError()("FileReviewUnitFailed", {
1044
- childErrorTag: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
1045
- message: Schema.String.check(Schema.isMaxLength(400))
1046
- }) {};
1047
- 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", {
1048
1296
  workId: ReviewPassId,
1049
1297
  reason: Schema.NonEmptyString.check(Schema.isMaxLength(600))
1050
1298
  }) {};
1051
- const FileReviewFailure = Schema.Union([FileReviewUnitFailed, FileReviewWorkRejected]);
1052
1299
  const staticGuidanceLines = (guidance) => {
1053
1300
  if (guidance === void 0) return [];
1054
1301
  return (typeof guidance === "string" ? [guidance] : guidance).filter((line) => line.length > 0);
@@ -1070,7 +1317,8 @@ const makeFileReviewerInstructions = (options = {}) => (brief) => {
1070
1317
  "Independently verify every candidate in the input. You did not receive another reviewer's transcript or reasoning; use only the candidate claim and bounded evidence.",
1071
1318
  "The evidence array contains the complete bounded unit, including neighboring changed code that may confirm or falsify a locally plausible claim.",
1072
1319
  "For each candidate, try to falsify it first. Confirm only when the cited behavior is supported and actionable. Reject unsupported, speculative, duplicate, or non-actionable candidates.",
1073
- "Return ONLY JSON with phase \"verification\", the exact workId/unitId, empty findings/concerns/fileSummaries arrays, and exactly one assessment per candidateId. Each assessment is {\"candidateId\": <exact id>, \"disposition\": <\"confirmed\" | \"rejected\">, \"rationale\": <bounded evidence-based reason>}. Never add or omit an id."
1320
+ "Return ONLY JSON with phase \"verification\", the exact workId/unitId, empty findings/concerns/fileSummaries arrays, and exactly one assessment per candidateId. Each assessment is {\"candidateId\": <exact id>, \"disposition\": <\"confirmed\" | \"rejected\">, \"suggestion\": <\"committable\" | \"not-committable\", present exactly when the candidate finding carries a suggestion>, \"rationale\": <bounded evidence-based reason>}. Never add or omit an id.",
1321
+ "Settle every carried suggestion independently of the claim: answer \"committable\" only when its text is the full replacement source for exactly lines startLine..endLine and nothing else — it compiles in context and preserves the finding's intent, never prose describing a change. Otherwise answer \"not-committable\"; the host then publishes the confirmed finding without its suggestion. Omit the assessment \"suggestion\" field for candidates without one."
1074
1322
  ].join("\n");
1075
1323
  const focus = brief.perspective === "risk-specialist" ? brief.riskCategories.length > 0 ? `This is a fresh specialist discovery pass. Concentrate on these host-classified risks without relying on another pass: ${brief.riskCategories.join(", ")}.` : "This is a fresh specialist discovery pass. The host found no keyword-classified category, so independently scrutinize authentication/authorization, security boundaries, durability, concurrency, credentials, and external side effects rather than treating classification silence as low risk." : "This is the general discovery pass. Review broadly for correctness, security, concurrency, resource, API, and error-handling defects.";
1076
1324
  return [
@@ -1078,13 +1326,13 @@ const makeFileReviewerInstructions = (options = {}) => (brief) => {
1078
1326
  focus,
1079
1327
  "The discovery evidence array contains every complete shard in the unit. Review every entry and every shard of a multi-shard path. A later independent verifier, not you, decides which candidates publish.",
1080
1328
  "When a non-anchored concern depends on one or more unit files, list 1-3 exact evidencePaths to bind the claim to scheduled evidence.",
1081
- `Return ONLY JSON with phase "discovery", the exact workId/unitId, up to 6 findings, up to 3 concerns shaped as {concern, evidencePaths}, one factual file summary per path (<= 240 chars), and an empty assessments array. Empty candidate arrays are valid; do not invent defects.`
1329
+ `Return ONLY JSON with phase "discovery", the exact workId/unitId, up to 6 findings, up to 3 concerns shaped as {concern, evidencePaths}, one factual file summary per path (<= 240 chars), and an empty assessments array. Empty candidate arrays are valid; do not invent defects.`,
1330
+ "Each finding is {\"path\": <a unit file path>, \"startLine\": <integer, an R-marked new-file line>, \"endLine\": <integer, >= startLine, same file, R-marked>, \"severity\": <\"blocking\" | \"important\" | \"nit\">, \"category\": <OPTIONAL problem-kind label>, \"title\": <string, <= 120 chars>, \"body\": <string, why it matters and what to do>, \"suggestion\": <string, OPTIONAL: replacement source code for exactly lines startLine..endLine, ready to commit>}.",
1331
+ "Include \"suggestion\" only when you are confident the replacement compiles and preserves intent; its text must contain the full replacement source for every line in the range and nothing else — never prose describing the change, which belongs in \"body\"."
1082
1332
  ].join("\n");
1083
1333
  };
1084
1334
  const fileReviewerInstructions = makeFileReviewerInstructions();
1085
1335
  const FileReviewToolkit = Toolkit.empty;
1086
- /** Compatibility export: the evidence-only child has no handler requirements. */
1087
- const FileReviewToolkitLayer = Layer.empty;
1088
1336
  const defaultFileReviewerPolicy = AgentPolicy.make({
1089
1337
  maxTurns: 6,
1090
1338
  maxToolCalls: 1,
@@ -1096,54 +1344,29 @@ const defaultFileReviewerPolicy = AgentPolicy.make({
1096
1344
  toolResultBounds: ToolResultBounds.make({ maxBytes: REVIEW_TOOL_RESULT_MAX_BYTES }),
1097
1345
  onExhaustion: "fail"
1098
1346
  });
1099
- const fileReviewPolicy = SubagentPolicy.make({
1100
- maxChildren: MAX_REVIEW_CHILDREN,
1101
- maxConcurrency: 4,
1102
- maxTurns: 6,
1103
- maxToolCalls: 1,
1104
- maxDuration: "6 minutes",
1105
- maxResultBytes: 256 * 1024
1106
- });
1107
- const mapFileReviewChildFailure = (failure) => FileReviewUnitFailed.make({
1108
- childErrorTag: failure._tag,
1109
- message: (failure.message ?? "").slice(0, 400)
1110
- });
1111
- const sameStrings = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
1112
- const rejectWork = (workId, reason) => FileReviewWorkRejected.make({
1113
- workId,
1114
- reason
1115
- });
1116
- /** Validate coordinator scheduling against the current deterministic plan. */
1117
- const prepareReviewBrief = (request) => Effect.gen(function* () {
1118
- const source = yield* PullRequestSource;
1119
- const mapSourceFailure = (failure) => rejectWork(request.workId, `pull-request source ${failure.operation} failed: ${failure.reason}`.slice(0, 600));
1120
- const files = yield* source.changedFiles.pipe(Effect.mapError(mapSourceFailure));
1121
- const plan = planReviewUnits(files, { totalChangedFiles: (yield* source.metadata.pipe(Effect.mapError(mapSourceFailure))).totalChangedFiles });
1122
- const unit = plan.units.find((candidate) => candidate.unitId === request.unitId);
1123
- 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");
1124
- if (request.phase === "discovery") {
1125
- const pass = plan.discoveryPasses.find((candidate) => candidate.passId === request.workId);
1126
- 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");
1127
- } else {
1128
- 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");
1129
- const candidateIds = /* @__PURE__ */ new Set();
1130
- const candidateSubjects = /* @__PURE__ */ new Set();
1131
- const allowed = new Set(unit.paths);
1132
- for (const candidate of request.candidates) {
1133
- const subjectKey = reviewCandidateSubjectKey(candidate);
1134
- 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");
1135
- candidateIds.add(candidate.candidateId);
1136
- candidateSubjects.add(subjectKey);
1137
- }
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"
1138
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* () {
1139
1364
  const byPath = new Map(files.map((file) => [file.path, file]));
1140
1365
  const evidence = [];
1141
1366
  for (const shard of unit.evidenceShards) {
1142
1367
  const file = byPath.get(shard.path);
1143
- if (file === void 0) return yield* rejectWork(request.workId, `planned evidence path is unavailable: ${shard.path}`);
1144
- const chunks = fileReviewEvidenceChunks(file);
1145
- const chunk = chunks[shard.ordinal - 1];
1146
- 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})`));
1147
1370
  evidence.push(FileReviewEvidence.make({
1148
1371
  shardId: shard.shardId,
1149
1372
  path: shard.path,
@@ -1154,169 +1377,274 @@ const prepareReviewBrief = (request) => Effect.gen(function* () {
1154
1377
  annotatedPatch: chunk.annotatedPatch
1155
1378
  }));
1156
1379
  }
1157
- return FileReviewBrief.make({
1158
- ...request,
1159
- evidence
1160
- });
1380
+ return evidence;
1161
1381
  });
1162
- const candidateOrdinal = (index) => String(index + 1).padStart(3, "0");
1163
- const projectReviewResult = (report, context, request) => {
1164
- if (context.budgetExhausted) return Effect.fail(rejectWork(report.workId, "review work exhausted its budget before exact settlement"));
1165
- 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"));
1166
- if (report.phase === "verification") {
1167
- 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"));
1168
- const expectedIds = new Set(request.candidates.map((candidate) => candidate.candidateId));
1169
- const assessedIds = /* @__PURE__ */ new Set();
1170
- for (const assessment of report.assessments) {
1171
- if (!expectedIds.has(assessment.candidateId) || assessedIds.has(assessment.candidateId)) return Effect.fail(rejectWork(report.workId, "verification output did not assess the exact candidate set"));
1172
- 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;
1173
1444
  }
1174
- if (assessedIds.size !== expectedIds.size) return Effect.fail(rejectWork(report.workId, "verification output did not assess the exact candidate set"));
1175
- return Effect.succeed(FileReviewUnitResult.make({
1176
- phase: report.phase,
1177
- workId: report.workId,
1178
- unitId: report.unitId,
1179
- candidates: [],
1180
- fileSummaries: [],
1181
- assessments: report.assessments
1182
- }));
1445
+ keptFindings.push(finding);
1183
1446
  }
1184
- if (report.assessments.length > 0) return Effect.fail(rejectWork(report.workId, "discovery output contained verification-only assessments"));
1185
- const allowed = new Set(request.paths);
1186
- 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"));
1187
- return Effect.gen(function* () {
1188
- const source = yield* PullRequestSource;
1189
- const mapSourceFailure = (failure) => rejectWork(request.workId, `pull-request source ${failure.operation} failed: ${failure.reason}`.slice(0, 600));
1190
- const files = yield* source.changedFiles.pipe(Effect.mapError(mapSourceFailure));
1191
- const metadata = yield* source.metadata.pipe(Effect.mapError(mapSourceFailure));
1192
- const anchorFiles = yield* source.anchorFiles.pipe(Effect.mapError(mapSourceFailure));
1193
- const unit = planReviewUnits(files, { totalChangedFiles: metadata.totalChangedFiles }).units.find((candidate) => candidate.unitId === request.unitId);
1194
- if (unit === void 0) return yield* rejectWork(request.workId, "scheduled review unit is no longer available");
1195
- for (const finding of report.findings) {
1196
- const violation = anchorViolation(finding, anchorFiles);
1197
- 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;
1198
1452
  }
1199
- const findingCandidates = report.findings.map((finding, index) => FindingCandidate.make({
1200
- candidateId: `${request.workId}:finding:${candidateOrdinal(index)}`,
1201
- workId: request.workId,
1202
- 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,
1203
1460
  finding,
1204
1461
  evidencePaths: [finding.path]
1205
- }));
1206
- const concernCandidates = report.concerns.map((candidate, index) => ConcernCandidate.make({
1207
- candidateId: `${request.workId}:concern:${candidateOrdinal(index)}`,
1208
- workId: request.workId,
1209
- 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,
1210
1466
  concern: candidate.concern,
1211
1467
  evidencePaths: candidate.evidencePaths
1212
- }));
1213
- return FileReviewUnitResult.make({
1214
- phase: report.phase,
1215
- workId: report.workId,
1216
- unitId: report.unitId,
1217
- candidates: [...findingCandidates, ...concernCandidates],
1218
- fileSummaries: report.fileSummaries,
1219
- assessments: []
1220
- });
1221
- });
1222
- };
1223
- const delegationDescription = "Run exactly one host-planned discovery or candidate-verification child. Copy every plan field and candidate verbatim; never retry failed work.";
1224
- const makeFileReviewDelegation = (child) => Subagent.define("delegate_file_review", {
1225
- description: delegationDescription,
1226
- target: child,
1227
- parameters: FileReviewRequest,
1228
- success: FileReviewUnitResult,
1229
- failure: FileReviewFailure,
1230
- failureMode: "return",
1231
- prepareInput: prepareReviewBrief,
1232
- projectResult: projectReviewResult,
1233
- policy: fileReviewPolicy
1234
- });
1235
- var ListReviewUnitsQuery = class extends Schema.Class("@effect-agent/pr-review/ListReviewUnitsQuery")({ scope: Schema.Literal("all") }) {};
1236
- const ListReviewUnits = Tool.make("list_review_units", {
1237
- description: "List deterministic bounded review units, explicit risk categories, every required discovery pass, and paths the pipeline cannot cover.",
1238
- parameters: ListReviewUnitsQuery,
1239
- success: ReviewUnitPlan,
1240
- failure: PullRequestSourceFailure,
1241
- failureMode: "error",
1242
- dependencies: [PullRequestSource]
1243
- }).annotate(ToolExecutionClass, "readonly");
1244
- const FanOutCoordinatorToolkit = Toolkit.make(ListReviewUnits);
1245
- const FanOutCoordinatorToolkitLayer = FanOutCoordinatorToolkit.toLayer({ list_review_units: () => Effect.gen(function* () {
1246
- const source = yield* PullRequestSource;
1247
- return planReviewUnits(yield* source.changedFiles, { totalChangedFiles: (yield* source.metadata).totalChangedFiles });
1248
- }) });
1249
- const makeFanOutReviewInstructions = (options = {}) => (mission) => {
1250
- const maxFindings = clampMaxFindings(options.maxFindings);
1251
- return [
1252
- `You coordinate the bounded multi-pass review of pull request #${mission.number} ("${mission.title}") in ${mission.repository}.`,
1253
- mission.body.length > 0 ? `Author description:\n${mission.body}` : "No author description.",
1254
- ...staticGuidanceLines(options.guidance),
1255
- "1. Call list_review_units exactly once.",
1256
- "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.",
1257
- "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.",
1258
- "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.",
1259
- `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}.`,
1260
- "No configured pipeline can prove absence of defects. Describe settled work, never an exhaustive or defect-free review."
1261
- ].join("\n");
1468
+ }))],
1469
+ fileSummaries: report.fileSummaries.filter((entry) => allowed.has(entry.path)),
1470
+ discarded
1471
+ };
1262
1472
  };
1263
- const fanOutReviewInstructions = makeFanOutReviewInstructions();
1264
- const defaultFanOutPolicy = AgentPolicy.make({
1265
- maxTurns: 7,
1266
- maxToolCalls: 25,
1267
- maxDuration: "20 minutes",
1268
- toolConcurrency: 4,
1269
- repeatedFailureLimit: 3,
1270
- tokenBudget: 4e5,
1271
- contextTokenLimit: 15e4,
1272
- onExhaustion: "final-answer"
1273
- });
1274
- const makeFileReviewerDefinition = (options = {}) => Agent.define("pr-review-worker", {
1275
- input: FileReviewBrief,
1276
- output: FileReviewReport,
1277
- instructions: makeFileReviewerInstructions(options),
1278
- toolkit: FileReviewToolkit,
1279
- policy: defaultFileReviewerPolicy,
1280
- description: "Perform one bounded discovery or independent candidate-verification pass over host-supplied pull-request evidence.",
1281
- metadata: {
1282
- deploymentClass: "E",
1283
- surface: "read-only",
1284
- stage: "discovery-verification"
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
+ }
1285
1517
  }
1286
- });
1287
- const delegationToolFor = (delegation) => delegation.tool.annotate(ToolExecutionClass, "readonly");
1288
- const makeFanOutReviewerDefinition = (options, delegation) => Agent.define("pr-fanout-reviewer", {
1289
- input: ReviewMission,
1290
- output: CodeReview,
1291
- instructions: makeFanOutReviewInstructions(options),
1292
- toolkit: Toolkit.make(ListReviewUnits, delegationToolFor(delegation)),
1293
- policy: defaultFanOutPolicy,
1294
- description: "Coordinate deterministic general/specialist discovery and independent candidate verification over bounded review units.",
1295
- metadata: {
1296
- deploymentClass: "E",
1297
- surface: "read-only",
1298
- delegation: "S1-attached",
1299
- assurance: "multi-pass"
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
+ }
1300
1558
  }
1301
- });
1302
- const makeFanOutReviewSuite = (options = {}) => {
1303
- const child = makeFileReviewerDefinition({ guidance: options.guidance });
1304
- const delegation = makeFileReviewDelegation(child);
1305
1559
  return {
1306
- child,
1307
- parent: makeFanOutReviewerDefinition(options, delegation),
1308
- delegation
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 : []
1309
1573
  };
1574
+ });
1575
+ const countNoun = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
1576
+ const composeSummary = (plan, assurance) => {
1577
+ const requiredDiscovery = assurance.requiredGeneralDiscoveryPasses + assurance.requiredSpecialistPasses;
1578
+ const completedDiscovery = assurance.completedGeneralDiscoveryPasses + assurance.completedSpecialistPasses;
1579
+ 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.`];
1580
+ 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.`);
1581
+ if (assurance.discardedInvalidFindings > 0) parts.push(`${countNoun(assurance.discardedInvalidFindings, "candidate")} discarded for anchors or paths outside the assigned evidence.`);
1582
+ 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.`);
1583
+ 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.");
1584
+ parts.push("No configured pipeline can prove absence of defects; this describes settled work only.");
1585
+ return parts.join(" ").slice(0, 4e3);
1310
1586
  };
1311
- const defaultSuite = makeFanOutReviewSuite();
1312
- const FileReviewer = defaultSuite.child;
1313
- const FanOutReviewer = defaultSuite.parent;
1314
- const fileReviewDelegation = defaultSuite.delegation;
1315
- const DelegateFileReview = delegationToolFor(fileReviewDelegation);
1316
- const FanOutReviewToolkit = FanOutReviewer.toolkit;
1317
- const FileReviewDelegationFailure = fileReviewDelegation.containedFailure;
1318
- const fanOutHandlersLayerFor = (delegation) => (childBinding) => SubagentRuntime.layer(delegation, childBinding, { mapChildFailure: mapFileReviewChildFailure });
1319
- const fanOutHandlersLayer = fanOutHandlersLayerFor(fileReviewDelegation);
1587
+ /**
1588
+ * Run the complete host-scheduled fan-out pipeline over one selected
1589
+ * changeset snapshot: plan, independent discovery, exact verification, and a
1590
+ * deterministic host-composed CodeReview from verifier-confirmed candidates
1591
+ * only. The verdict is derived from confirmed severities, never model prose.
1592
+ */
1593
+ const runFanOutReview = (binding, input) => Effect.gen(function* () {
1594
+ const plan = planReviewUnits(input.files, { totalChangedFiles: input.totalChangedFiles });
1595
+ const passesByUnit = /* @__PURE__ */ new Map();
1596
+ for (const pass of plan.discoveryPasses) {
1597
+ const passes = passesByUnit.get(pass.unitId) ?? [];
1598
+ passes.push(pass);
1599
+ passesByUnit.set(pass.unitId, passes);
1600
+ }
1601
+ const outcomes = yield* Effect.forEach(plan.units, (unit) => reviewUnit(binding, unit, passesByUnit.get(unit.unitId) ?? [], input), { concurrency: 4 });
1602
+ const failedPasses = outcomes.flatMap((outcome) => outcome.failedPasses);
1603
+ const unsettledCandidates = outcomes.reduce((total, outcome) => total + outcome.unsettledCandidates, 0);
1604
+ const reasons = [];
1605
+ if (failedPasses.length > 0) reasons.push(boundedListReason("configured review passes did not settle", failedPasses.map((pass) => `${pass.workId} (${pass.errorTag})`)));
1606
+ if (unsettledCandidates > 0) reasons.push(`${unsettledCandidates} discovered candidate(s) did not receive exact verification`);
1607
+ const requiredSpecialistPasses = plan.discoveryPasses.filter((pass) => pass.perspective === "risk-specialist").length;
1608
+ const confirmed = outcomes.flatMap((outcome) => outcome.confirmed);
1609
+ const assurance = ReviewAssurance.make({
1610
+ status: reasons.length === 0 ? "settled" : "incomplete",
1611
+ requiredGeneralDiscoveryPasses: plan.discoveryPasses.length - requiredSpecialistPasses,
1612
+ completedGeneralDiscoveryPasses: outcomes.reduce((total, outcome) => total + outcome.completedGeneralPasses, 0),
1613
+ requiredSpecialistPasses,
1614
+ completedSpecialistPasses: outcomes.reduce((total, outcome) => total + outcome.completedSpecialistPasses, 0),
1615
+ requiredVerificationPasses: outcomes.reduce((total, outcome) => total + outcome.requiredVerificationPasses, 0),
1616
+ completedVerificationPasses: outcomes.reduce((total, outcome) => total + outcome.completedVerificationPasses, 0),
1617
+ discoveredCandidates: outcomes.reduce((total, outcome) => total + outcome.discoveredCandidates, 0),
1618
+ confirmedCandidates: confirmed.length,
1619
+ rejectedCandidates: outcomes.reduce((total, outcome) => total + outcome.rejectedCandidates, 0),
1620
+ unsettledCandidates,
1621
+ discardedInvalidFindings: outcomes.reduce((total, outcome) => total + outcome.discardedFindings, 0),
1622
+ failedPasses,
1623
+ reasons
1624
+ });
1625
+ const findings = rankAndDedupeFindings(confirmed.flatMap(({ assessment, candidate }) => candidate._tag === "FindingCandidate" ? [confirmedFindingForPublication(assessment, candidate)] : []));
1626
+ const concerns = rankAndDedupeConcerns(confirmed.flatMap(({ candidate }) => candidate._tag === "ConcernCandidate" ? [candidate.concern] : []));
1627
+ const walkthrough = outcomes.flatMap((outcome) => outcome.walkthrough);
1628
+ const blocking = findings.some((finding) => finding.severity === "blocking") || concerns.some((concern) => concern.severity === "blocking");
1629
+ return {
1630
+ review: CodeReview.make({
1631
+ summary: composeSummary(plan, assurance),
1632
+ verdict: blocking ? "request-changes" : findings.length > 0 || concerns.length > 0 ? "comment" : "approve",
1633
+ findings,
1634
+ ...concerns.length === 0 ? {} : { concerns },
1635
+ ...walkthrough.length === 0 ? {} : { walkthrough }
1636
+ }),
1637
+ assurance,
1638
+ plan,
1639
+ unreviewedPaths: [.../* @__PURE__ */ new Set([
1640
+ ...outcomes.flatMap((outcome) => outcome.unreviewedPaths),
1641
+ ...plan.unassignedPaths,
1642
+ ...plan.partialEvidencePaths,
1643
+ ...plan.undiffablePaths
1644
+ ])].sort(),
1645
+ turns: outcomes.reduce((total, outcome) => total + outcome.turns, 0)
1646
+ };
1647
+ });
1320
1648
  //#endregion
1321
1649
  //#region src/internal/fingerprint.ts
1322
1650
  const MARKER_PREFIX = "<!-- effect-agent-pr-review fingerprint=sha256:";
@@ -1381,16 +1709,21 @@ var StoredReviewConcern = class extends Schema.Class("@effect-agent/pr-review/St
1381
1709
  title: Schema.NonEmptyString.check(Schema.isMaxLength(120)),
1382
1710
  body: StoredText
1383
1711
  }) {};
1712
+ /** The carried-scope bound; a run that cannot fit its leftovers publishes no state. */
1713
+ const MAX_STORED_UNREVIEWED_PATHS = 100;
1384
1714
  /**
1385
- * Versioned state embedded only after complete input assignment and settled
1386
- * configured review assurance. The head plus full-scope fingerprint forms an
1387
- * incremental baseline; an absent unresolved item never means the path is
1388
- * defect-free. The `acceptedScopeFingerprint` name is retained for wire
1389
- * compatibility. Storing hundreds of path strings separately would not fit
1390
- * GitHub's bounded review body in the worst case.
1715
+ * Versioned state embedded after EVERY completed run that can be signed. The
1716
+ * head plus full-scope fingerprint forms an incremental baseline; an absent
1717
+ * unresolved item never means the path is defect-free. `unreviewedPaths`
1718
+ * carries retryable review gaps (failed passes) forward so the next
1719
+ * incremental run re-reviews exactly them plus the new delta — the baseline
1720
+ * advances monotonically instead of freezing on one flaky pass and reopening
1721
+ * the whole post-baseline scope. The `acceptedScopeFingerprint` name is
1722
+ * retained for wire compatibility. Storing hundreds of path strings
1723
+ * separately would not fit GitHub's bounded review body in the worst case.
1391
1724
  */
1392
1725
  var ReviewState = class extends Schema.Class("@effect-agent/pr-review/ReviewState")({
1393
- version: Schema.Literal(1),
1726
+ version: Schema.Literal(2),
1394
1727
  repository: Schema.NonEmptyString.check(Schema.isMaxLength(200)),
1395
1728
  pullRequestNumber: Schema.Int.check(Schema.isGreaterThan(0)),
1396
1729
  baseRef: Schema.NonEmptyString.check(Schema.isMaxLength(300)),
@@ -1405,6 +1738,14 @@ var ReviewState = class extends Schema.Class("@effect-agent/pr-review/ReviewStat
1405
1738
  })),
1406
1739
  unresolvedFindings: Schema.Array(StoredReviewFinding).check(Schema.isMaxLength(20)),
1407
1740
  unresolvedConcerns: Schema.Array(StoredReviewConcern).check(Schema.isMaxLength(10)),
1741
+ /** Retryable review gaps carried into the next incremental run's scope. */
1742
+ unreviewedPaths: Schema.Array(ChangedPath).check(Schema.isMaxLength(100)),
1743
+ /**
1744
+ * True only when the producing run had complete input coverage, no
1745
+ * unsettled pass, and nothing carried. Skip-unchanged authority: an
1746
+ * unchanged patch may skip re-review only over a settled state.
1747
+ */
1748
+ settled: Schema.Boolean,
1408
1749
  lastReviewMode: ReviewScopeMode
1409
1750
  }) {};
1410
1751
  const toStoredFinding = (finding) => StoredReviewFinding.make({
@@ -1433,12 +1774,12 @@ const fromStoredConcern = (concern) => ReviewConcern.make({
1433
1774
  title: concern.title,
1434
1775
  body: concern.body
1435
1776
  });
1436
- const STATE_MARKER_PREFIX = "<!-- effect-agent-pr-review state-v1:";
1777
+ const STATE_MARKER_PREFIX = "<!-- effect-agent-pr-review state-v2:";
1437
1778
  const STATE_MARKER_SUFFIX = " -->";
1438
- const STATE_MARKER_PATTERN = /(?:^|\n)<!-- effect-agent-pr-review state-v1:([A-Za-z0-9+/]+={0,2})\.([0-9a-f]{64}) -->$/;
1439
- const STATE_SIGNATURE_DOMAIN = "effect-agent-pr-review/state-v1\0";
1779
+ const STATE_MARKER_PATTERN = /(?:^|\n)<!-- effect-agent-pr-review state-v2:([A-Za-z0-9+/]+={0,2})\.([0-9a-f]{64}) -->$/;
1780
+ const STATE_SIGNATURE_DOMAIN = "effect-agent-pr-review/state-v2\0";
1440
1781
  const MAX_REVIEW_STATE_MARKER_CHARS = 24e3;
1441
- 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"));
1782
+ 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"));
1442
1783
  var ReviewStateAuthenticationFailure = class extends Schema.TaggedError()("ReviewStateAuthenticationFailure", {
1443
1784
  operation: Schema.Literals(["sign", "verify"]),
1444
1785
  reason: Schema.NonEmptyString.check(Schema.isMaxLength(2048))
@@ -1591,13 +1932,17 @@ const selectReviewRange = (input) => {
1591
1932
  const currentPaths = new Set(input.fullFiles.flatMap((file) => file.previousPath === void 0 ? [file.path] : [file.path, file.previousPath]));
1592
1933
  const selectedByPath = /* @__PURE__ */ new Map();
1593
1934
  for (const file of comparison.files) if (currentPaths.has(file.path) || file.previousPath !== void 0 && currentPaths.has(file.previousPath)) selectedByPath.set(file.path, file);
1594
- if (input.priorState.baseSha !== input.current.baseSha) {
1595
- for (const file of input.fullFiles) if (affectedPaths.has(file.path) || file.previousPath !== void 0 && affectedPaths.has(file.previousPath)) selectedByPath.set(file.path, file);
1935
+ const carriedPaths = new Set(input.priorState.unreviewedPaths.filter((path) => currentPaths.has(path)));
1936
+ for (const path of carriedPaths) affectedPaths.add(path);
1937
+ const rescuePaths = input.priorState.baseSha !== input.current.baseSha;
1938
+ if (rescuePaths || carriedPaths.size > 0) {
1939
+ for (const file of input.fullFiles) if (rescuePaths && (affectedPaths.has(file.path) || file.previousPath !== void 0 && affectedPaths.has(file.previousPath)) || carriedPaths.has(file.path) || file.previousPath !== void 0 && carriedPaths.has(file.previousPath)) selectedByPath.set(file.path, file);
1596
1940
  }
1597
1941
  const selectedFiles = [...selectedByPath.values()].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
1942
+ const carriedReason = carriedPaths.size === 0 ? "" : `; retrying ${carriedPaths.size} carried unreviewed path(s)`;
1598
1943
  return {
1599
1944
  mode: "incremental",
1600
- reason: `changes since settled review head ${input.priorState.reviewedHeadSha.slice(0, 7)}${baseReason}`,
1945
+ reason: `changes since reviewed head ${input.priorState.reviewedHeadSha.slice(0, 7)}${baseReason}${carriedReason}`,
1601
1946
  files: selectedFiles,
1602
1947
  affectedPaths: [...affectedPaths].sort(),
1603
1948
  totalFiles: selectedFiles.length,
@@ -1696,7 +2041,7 @@ var ReviewRetirementReport = class extends Schema.Class("@effect-agent/pr-review
1696
2041
  const findingIdentity = (finding) => `${finding.path}\u0000${finding.startLine}\u0000${finding.endLine}\u0000${finding.title}`;
1697
2042
  const REVIEW_METADATA_PATTERN = /<!-- effect-agent-pr-review metadata\n[\s\S]*?\n-->/g;
1698
2043
  const FINGERPRINT_PATTERN = /<!-- effect-agent-pr-review fingerprint=sha256:[0-9a-f]{64} -->/g;
1699
- const STATE_PATTERN = /<!-- effect-agent-pr-review state-v1:[A-Za-z0-9+/]+={0,2}\.[0-9a-f]{64} -->/g;
2044
+ const STATE_PATTERN = /<!-- effect-agent-pr-review state-v\d+:[A-Za-z0-9+/]+={0,2}\.[0-9a-f]{64} -->/g;
1700
2045
  const RETIRED_ORIGINAL_PATTERN = /<!-- effect-agent-pr-review retired-original:start -->\n([\s\S]*?)\n<!-- effect-agent-pr-review retired-original:end -->/;
1701
2046
  const MACHINE_COMMENT_PATTERN = new RegExp(`${REVIEW_METADATA_PATTERN.source}|${FINGERPRINT_PATTERN.source}|${STATE_PATTERN.source}`, "g");
1702
2047
  const VERDICT_CALLOUT_PATTERN = /^(?:> \[!(?:CAUTION|IMPORTANT)\]\n> [^\n]*(?:\n> [^\n]*)*|> (?:ℹ️|✅)[^\n]*)\n*/;
@@ -2269,6 +2614,6 @@ const fingerprintUnchanged = (current) => Effect.gen(function* () {
2269
2614
  return Option.isSome(latest) && latest.value === current;
2270
2615
  });
2271
2616
  //#endregion
2272
- export { DiscoveredConcern as $, ChangedFile as $n, ReviewUnit as $t, ReviewStateAuthenticator as A, ReviewMission as An, fanOutHandlersLayer as At, selectedPullRequestSourceLayer as B, makeReviewInstructions as Bn, reviewCandidateSubjectKey as Bt, MAX_REVIEW_STATE_MARKER_CHARS as C, MAX_WALKTHROUGH_SUMMARY_CHARS as Cn, MAX_UNIT_CANDIDATES as Ct, ReviewScopeMode as D, ReadFileDiff as Dn, ReviewWorkPhase as Dt, ReviewMode as E, ReadFile as En, ReviewWorkPerspective as Et, buildProfileMission as F, clampMaxFindings as Fn, fileReviewerInstructions as Ft, webCryptoReviewStateAuthenticatorLayer as G, MAX_CHANGED_FILES as Gn, MAX_UNIT_EVIDENCE_SHARDS as Gt, toStoredFinding as H, readFileHandler as Hn, MAX_MERGED_FINDINGS as Ht, computeProfileFingerprint as I, defaultReviewPolicy as In, makeFanOutReviewInstructions as It, extractFingerprint as J, PullRequestSource as Jn, ReviewDiscoveryPerspective as Jt, FINGERPRINT_MARKER_LENGTH as K, MAX_FILE_CHARS as Kn, MAX_UNIT_FILES as Kt, fromStoredConcern as L, fileDiffView as Ln, makeFanOutReviewSuite as Lt, ReviewStateMarkerTooLarge as M, ReviewToolkitLayer as Mn, fanOutReviewInstructions as Mt, StoredReviewConcern as N, ReviewVerdict as Nn, fileReviewDelegation as Nt, ReviewState as O, ReviewConcern as On, defaultFanOutPolicy as Ot, StoredReviewFinding as P, WalkthroughEntry as Pn, fileReviewPolicy as Pt, DelegateFileReview as Q, anchorViolation as Qn, ReviewRiskCategory as Qt, fromStoredFinding as R, fileReviewEvidenceChunks as Rn, makeFileReviewerInstructions as Rt, GitCommitSha as S, MAX_WALKTHROUGH_ENTRIES as Sn, MAX_REVIEW_CHILDREN as St, ReviewHeadComparison as T, REVIEW_TOOL_RESULT_MAX_BYTES as Tn, ReviewCandidateId as Tt, unavailableReviewStateAuthenticatorLayer as U, resolveGuidance as Un, MAX_REPORTED_UNASSIGNED_EVIDENCE_SHARDS as Ut, toStoredConcern as V, readFileDiffHandler as Vn, MAX_FILE_EVIDENCE_CHARS as Vt, validateReviewState as W, reviewInstructions as Wn, MAX_REVIEW_UNITS as Wt, CandidateAssessment as X, ReviewInputViolation as Xn, ReviewEvidenceShardId as Xt, renderFingerprintMarker as Y, PullRequestSourceFailure as Yn, ReviewEvidenceShard as Yt, ConcernCandidate as Z, normalizeRepoRelativePath as Zn, ReviewPassId as Zt, ReviewRetirementHost as _, ListChangedFiles as _n, ListReviewUnits as _t, PriorReviews as a, findingAnchorInUnitEvidence as an, hasReviewableContent as ar, FileReviewDelegationFailure as at, hasReviewMetadataMarker as b, MAX_FINDINGS as bn, MAX_CHILD_FINDINGS as bt, fingerprintUnchanged as c, ChangedFileSummary as cn, renderReviewContent as cr, FileReviewReport as ct, gitHubReviewPublisherLayer as d, FileDiffQuery as dn, FileReviewToolkitLayer as dt, ReviewUnitId as en, ChangedFileStatus as er, FanOutCoordinatorToolkit as et, gitHubReviewRetirementHostLayer as f, FileDiffView as fn, FileReviewUnitFailed as ft, ReviewRetirementFailure as g, FindingSeverity as gn, FindingCandidate as gt, RetirableReviewComment as h, FindingCategory as hn, FileReviewer as ht, PriorReviewLookupFailure as i, classifyReviewRisks as in, commentableLines as ir, FileReviewBrief as it, ReviewStateMarker as j, ReviewToolkit as jn, fanOutHandlersLayerFor as jt, ReviewStateAuthenticationFailure as k, ReviewFinding as kn, defaultFileReviewerPolicy as kt, gitHubPriorReviewsLayer as l, ChangedFilesView as ln, FileReviewRequest as lt, RetirableReview as m, FileSliceQuery as mn, FileReviewWorkRejected as mt, GitHubApiFailure as n, UNIT_CHANGED_LINE_BUDGET as nn, MAX_REVIEW_CONTENT_CHARS as nr, FanOutReviewToolkit as nt, PublishedReview as o, planReviewUnits as on, isReviewableFile as or, FileReviewEvidence as ot, parseGitHubSubmittedAt as p, FileSlice as pn, FileReviewUnitResult as pt, computeChangesetFingerprint as q, PullRequestMetadata as qn, ReviewDiscoveryPass as qt, GitHubReviewTarget as r, UNIT_EVIDENCE_CHAR_BUDGET as rn, annotatePatch as rr, FanOutReviewer as rt, ReviewPublisher as s, rankAndDedupeFindings as sn, parsePatch as sr, FileReviewFailure as st, DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN as t, ReviewUnitPlan as tn, ChangedPath as tr, FanOutCoordinatorToolkitLayer as tt, gitHubPullRequestSourceLayer as u, CodeReview as un, FileReviewToolkit as ut, ReviewRetirementReport as v, ListChangedFilesQuery as vn, ListReviewUnitsQuery as vt, ReviewExecutionContext as w, PullRequestReviewer as wn, ReviewCandidate as wt, retireStaleReviews as x, MAX_PATCH_CHARS as xn, MAX_FILE_REVIEW_TOOL_CALLS as xt, decideReviewRetirement as y, MAX_CONCERNS as yn, MAX_CHILD_CONCERNS as yt, selectReviewRange as z, listChangedFilesHandler as zn, mapFileReviewChildFailure as zt };
2617
+ export { DiscoveredConcern as $, commentableLines as $n, boundedListReason as $t, ReviewStateAuthenticationFailure as A, clampMaxFindings as An, MAX_UNIT_FILES as At, selectReviewRange as B, MAX_CHANGED_FILES as Bn, UNIT_CHANGED_LINE_BUDGET as Bt, MAX_REVIEW_STATE_MARKER_CHARS as C, ReviewConcern as Cn, reviewCandidateSubjectKey as Ct, ReviewMode as D, ReviewToolkitLayer as Dn, MAX_REPORTED_UNASSIGNED_EVIDENCE_SHARDS as Dt, ReviewHeadComparison as E, ReviewToolkit as En, MAX_MERGED_FINDINGS as Et, StoredReviewFinding as F, makeReviewInstructions as Fn, ReviewPassId as Ft, validateReviewState as G, ReviewInputViolation as Gn, rankAndDedupeConcerns as Gt, toStoredConcern as H, PullRequestMetadata as Hn, classifyReviewRisks as Ht, buildProfileMission as I, readFileDiffHandler as In, ReviewRiskCategory as It, computeChangesetFingerprint as J, ChangedFile as Jn, FailedReviewUnit as Jt, webCryptoReviewStateAuthenticatorLayer as K, normalizeRepoRelativePath as Kn, rankAndDedupeFindings as Kt, computeProfileFingerprint as L, readFileHandler as Ln, ReviewUnit as Lt, ReviewStateMarker as M, fileDiffView as Mn, ReviewDiscoveryPerspective as Mt, ReviewStateMarkerTooLarge as N, fileReviewEvidenceChunks as Nn, ReviewEvidenceShard as Nt, ReviewScopeMode as O, ReviewVerdict as On, MAX_REVIEW_UNITS as Ot, StoredReviewConcern as P, listChangedFilesHandler as Pn, ReviewEvidenceShardId as Pt, ConcernCandidate as Q, annotatePatch as Qn, assessFlatReview as Qt, fromStoredConcern as R, resolveGuidance as Rn, ReviewUnitId as Rt, GitCommitSha as S, ReadFileDiff as Sn, makeFileReviewerInstructions as St, ReviewExecutionContext as T, ReviewMission as Tn, MAX_FILE_EVIDENCE_CHARS as Tt, toStoredFinding as U, PullRequestSource as Un, findingAnchorInUnitEvidence as Ut, selectedPullRequestSourceLayer as V, MAX_FILE_CHARS as Vn, UNIT_EVIDENCE_CHAR_BUDGET as Vt, unavailableReviewStateAuthenticatorLayer as W, PullRequestSourceFailure as Wn, planReviewUnits as Wt, renderFingerprintMarker as X, ChangedPath as Xn, ReviewCoverage as Xt, extractFingerprint as Y, ChangedFileStatus as Yn, ReviewAssurance as Yt, CandidateAssessment as Z, MAX_REVIEW_CONTENT_CHARS as Zn, ReviewInputCoverage as Zt, ReviewRetirementHost as _, MAX_WALKTHROUGH_ENTRIES as _n, assessmentSettlesSuggestionExactly as _t, PriorReviews as a, CodeReview as an, FindingCandidate as at, hasReviewMetadataMarker as b, REVIEW_TOOL_RESULT_MAX_BYTES as bn, fileReviewerInstructions as bt, fingerprintUnchanged as c, FileSlice as cn, MAX_FILE_REVIEW_TOOL_CALLS as ct, gitHubReviewPublisherLayer as d, FindingSeverity as dn, REVIEW_UNIT_CONCURRENCY as dt, compatibilityCoverage as en, hasReviewableContent as er, FileReviewBrief as et, gitHubReviewRetirementHostLayer as f, ListChangedFiles as fn, ReviewCandidate as ft, ReviewRetirementFailure as g, MAX_PATCH_CHARS as gn, ReviewWorkPhase as gt, RetirableReviewComment as h, MAX_FINDINGS as hn, ReviewWorkPerspective as ht, PriorReviewLookupFailure as i, ChangedFilesView as in, FileReviewer as it, ReviewStateAuthenticator as j, defaultReviewPolicy as jn, ReviewDiscoveryPass as jt, ReviewState as k, WalkthroughEntry as kn, MAX_UNIT_EVIDENCE_SHARDS as kt, gitHubPriorReviewsLayer as l, FileSliceQuery as ln, MAX_REVIEW_CHILDREN as lt, RetirableReview as m, MAX_CONCERNS as mn, ReviewPassMisbehaved as mt, GitHubApiFailure as n, flatAssurance as nn, parsePatch as nr, FileReviewReport as nt, PublishedReview as o, FileDiffQuery as on, MAX_CHILD_CONCERNS as ot, parseGitHubSubmittedAt as p, ListChangedFilesQuery as pn, ReviewCandidateId as pt, FINGERPRINT_MARKER_LENGTH as q, anchorViolation as qn, FailedReviewPass as qt, GitHubReviewTarget as r, ChangedFileSummary as rn, renderReviewContent as rr, FileReviewToolkit as rt, ReviewPublisher as s, FileDiffView as sn, MAX_CHILD_FINDINGS as st, DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN as t, fanOutInputCoverage as tn, isReviewableFile as tr, FileReviewEvidence as tt, gitHubPullRequestSourceLayer as u, FindingCategory as un, MAX_UNIT_CANDIDATES as ut, ReviewRetirementReport as v, MAX_WALKTHROUGH_SUMMARY_CHARS as vn, confirmedFindingForPublication as vt, MAX_STORED_UNREVIEWED_PATHS as w, ReviewFinding as wn, runFanOutReview as wt, retireStaleReviews as x, ReadFile as xn, makeFileReviewerDefinition as xt, decideReviewRetirement as y, PullRequestReviewer as yn, defaultFileReviewerPolicy as yt, fromStoredFinding as z, reviewInstructions as zn, ReviewUnitPlan as zt };
2273
2618
 
2274
- //# sourceMappingURL=github-DSqZp3Ce.mjs.map
2619
+ //# sourceMappingURL=github-BbwYzNrC.mjs.map