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

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.
@@ -169,6 +169,18 @@ const annotatePatch = (patch) => {
169
169
  return output.join("\n");
170
170
  };
171
171
  //#endregion
172
+ //#region src/internal/anchors.ts
173
+ /** Why a finding cannot anchor to the current new-version diff, if any. */
174
+ const anchorViolation = (finding, files) => {
175
+ const file = files.find((candidate) => candidate.path === finding.path);
176
+ if (file === void 0) return "path is not part of the changeset";
177
+ if (file.patch === void 0) return "file has no anchorable textual diff";
178
+ if (finding.endLine < finding.startLine) return "endLine precedes startLine";
179
+ if (finding.endLine - finding.startLine + 1 > 100) return "range is implausibly large";
180
+ const anchors = commentableLines(file.patch);
181
+ for (let line = finding.startLine; line <= finding.endLine; line += 1) if (!anchors.has(line)) return `line ${line} is not part of the diff`;
182
+ };
183
+ //#endregion
172
184
  //#region src/internal/source.ts
173
185
  /** Reading a file head version larger than this is refused, never truncated silently. */
174
186
  const MAX_FILE_CHARS = 2e5;
@@ -235,7 +247,7 @@ var PullRequestSource = class extends Context.Service()("@effect-agent/pr-review
235
247
  const MAX_FINDINGS = 20;
236
248
  /** The hard non-anchored-concerns bound carried by the CodeReview schema. */
237
249
  const MAX_CONCERNS = 10;
238
- /** Annotated patches larger than this are truncated with an explicit marker. */
250
+ /** Maximum characters in one deterministic model-visible evidence chunk. */
239
251
  const MAX_PATCH_CHARS = 6e4;
240
252
  /** The encoded Tool result must retain one complete bounded content fallback. */
241
253
  const REVIEW_TOOL_RESULT_MAX_BYTES = 2 * 1024 * 1024;
@@ -287,6 +299,53 @@ var FileDiffView = class extends Schema.Class("@effect-agent/pr-review/FileDiffV
287
299
  annotatedPatch: Schema.String,
288
300
  truncated: Schema.Boolean
289
301
  }) {};
302
+ /**
303
+ * Split complete model-visible evidence at deterministic line boundaries.
304
+ * A pathological single line is hard-sliced so every character is still
305
+ * assigned and every chunk remains within the provider-independent bound.
306
+ */
307
+ const boundedEvidenceChunks = (evidence) => {
308
+ if (evidence.length <= 6e4) return [evidence];
309
+ const chunks = [];
310
+ let offset = 0;
311
+ while (offset < evidence.length) {
312
+ let end = Math.min(offset + MAX_PATCH_CHARS, evidence.length);
313
+ if (end < evidence.length) {
314
+ const boundary = evidence.lastIndexOf("\n", end - 1);
315
+ if (boundary >= offset) end = boundary + 1;
316
+ }
317
+ if (end === offset) end = Math.min(offset + MAX_PATCH_CHARS, evidence.length);
318
+ chunks.push(evidence.slice(offset, end));
319
+ offset = end;
320
+ }
321
+ return chunks;
322
+ };
323
+ /** Complete bounded evidence chunks used by deterministic fan-out planning. */
324
+ const fileReviewEvidenceChunks = (file) => {
325
+ const contentEvidence = renderReviewContent(file);
326
+ const reviewMode = file.patch !== void 0 ? "diff" : contentEvidence !== void 0 ? "content" : "unavailable";
327
+ const annotated = file.patch === void 0 ? contentEvidence ?? "" : annotatePatch(file.patch);
328
+ return boundedEvidenceChunks(annotated).map((annotatedPatch) => ({
329
+ reviewMode,
330
+ annotatedPatch
331
+ }));
332
+ };
333
+ /** Host-owned rendering of one changed file's bounded review evidence. */
334
+ const fileDiffView = (file) => {
335
+ const chunks = fileReviewEvidenceChunks(file);
336
+ const first = chunks[0] ?? {
337
+ reviewMode: "unavailable",
338
+ annotatedPatch: ""
339
+ };
340
+ const truncated = first.reviewMode === "diff" && chunks.length > 1;
341
+ return FileDiffView.make({
342
+ path: file.path,
343
+ status: file.status,
344
+ reviewMode: first.reviewMode,
345
+ annotatedPatch: truncated ? `${first.annotatedPatch}\n[diff truncated]` : first.annotatedPatch,
346
+ truncated
347
+ });
348
+ };
290
349
  const ReadFileDiff = Tool.make("read_file_diff", {
291
350
  description: "Read one changed file's review evidence. A normal unified diff marks valid anchors as R<number>. When GitHub omitted the diff, bounded base/head content is returned with B/H line labels for review but no valid inline anchors.",
292
351
  parameters: FileDiffQuery,
@@ -352,17 +411,7 @@ const readFileDiffHandler = (query) => Effect.gen(function* () {
352
411
  input: relative,
353
412
  reason: "Path is not part of this pull request's changeset."
354
413
  });
355
- const contentEvidence = renderReviewContent(file);
356
- const reviewMode = file.patch !== void 0 ? "diff" : contentEvidence !== void 0 ? "content" : "unavailable";
357
- const annotated = file.patch === void 0 ? contentEvidence ?? "" : annotatePatch(file.patch);
358
- const truncated = reviewMode === "diff" && annotated.length > MAX_PATCH_CHARS;
359
- return FileDiffView.make({
360
- path: file.path,
361
- status: file.status,
362
- reviewMode,
363
- annotatedPatch: truncated ? `${annotated.slice(0, MAX_PATCH_CHARS)}\n[diff truncated]` : annotated,
364
- truncated
365
- });
414
+ return fileDiffView(file);
366
415
  });
367
416
  /**
368
417
  * The `read_file` handler, shared verbatim by the flat reviewer's toolkit
@@ -489,15 +538,15 @@ const makeReviewInstructions = (options = {}) => (mission) => {
489
538
  mission.body.length > 0 ? `Author description:\n${mission.body}` : "The author provided no description.",
490
539
  ...resolveGuidance(options.guidance, mission),
491
540
  "Work in this order:",
492
- "1. Call list_changed_files once to see the changeset. That list is your COMPLETE review scope: in incremental reviews it is deliberately a subset of the pull request's full diff (totalFiles counts the whole pull request), and everything it omits was already reviewed or excluded.",
493
- "2. Call read_file_diff for every file you review. A normal diff marks new-version anchors as R<number>; only those numbers are valid startLine/endLine values. When GitHub omitted a diff, the tool may return bounded base/head content marked B/H instead. Review that content, but report its defects as non-anchored concerns because B/H lines cannot anchor GitHub comments. Never anchor a finding to a removed (-), B, or H line.",
541
+ "1. Call list_changed_files once to see the selected input scope. In incremental reviews it is deliberately a subset of the pull request's full diff (totalFiles counts the whole pull request); omitted paths belong to settled prior scope or explicit host exclusions, not to this run.",
542
+ "2. Call read_file_diff for every listed file. A normal diff marks new-version anchors as R<number>; only those numbers are valid startLine/endLine values. When GitHub omitted a diff, the tool may return bounded base/head content marked B/H instead. Review that content, but report its defects as non-anchored concerns because B/H lines cannot anchor GitHub comments. Never anchor a finding to a removed (-), B, or H line.",
494
543
  "3. Call read_file when you need surrounding context the diff does not show. ONLY listed files are readable — read_file_diff and read_file both return a failed result for any other path (an import, a neighbor, a file named in the description). Do not request or retry unlisted paths; reason from the visible diffs instead and note the gap honestly in your summary when it matters.",
495
544
  "4. Review for real defects first: correctness, security, concurrency, resource leaks, error handling, API misuse. Style nits are least important. Do not praise; do not restate the diff.",
496
545
  "When the diff adds or changes a test, check that it can actually fail: a test that would still pass with the bug present is theatre, not coverage. The usual tell is a loose assertion standing where an exact one belongs — >= or a truthiness check over an expected value, or a snapshot that absorbs whatever it is handed.",
497
546
  "Go shallow only when the diff has no behavioral surface at all: doc typos, formatting, lockfile or generated-code regeneration, a mechanical rename. Line count is not the signal — a one-line change to auth, money, SQL, a comparison operator, or a config default is not trivial.",
498
547
  "Drop bloat-shaped findings before reporting: defensive checks for cases that cannot happen, abstractions used once, comments restating obvious code, tests asserting tautologies, just-in-case guards. A finding must be sound, correct, and worth acting on; prefer an explicit keep over an invented finding.",
499
548
  "5. After collecting anchored findings, deliberately scan for concerns with NO line to point at: deletion or cleanup plans for code the diff replaces, rollout or migration sequencing, coverage gaps the diff implies but does not add, scope questions only the author can answer. Report each as a \"concern\", never as a finding with an invented anchor; report none when none exist.",
500
- `6. Write a walkthrough: for every file you reviewed, one factual sentence (<= 240 chars) describing what changed in that file — written for a reader scanning the pull request, never restating the diff line by line. Use only paths from list_changed_files; invented paths are dropped.`,
549
+ `6. Write a walkthrough: for every file whose evidence you examined, one factual sentence (<= 240 chars) describing what changed in that file — written for a reader scanning the pull request, never restating the diff line by line. Use only paths from list_changed_files; invented paths are dropped.`,
501
550
  "7. Then return ONLY a JSON object — no Markdown fences, no prose before or after — exactly this shape: {\"summary\": <string, 1-3 paragraphs of overall assessment>, \"verdict\": <\"approve\" | \"comment\" | \"request-changes\">, \"findings\": [{\"path\": <string, a changed file path>, \"startLine\": <integer, an R-marked new-file line>, \"endLine\": <integer, >= startLine, same file, R-marked>, \"severity\": <\"blocking\" | \"important\" | \"nit\">, \"category\": <OPTIONAL: \"correctness\" | \"security\" | \"concurrency\" | \"performance\" | \"resources\" | \"error-handling\" | \"testing\" | \"maintainability\" | \"style\" | \"docs\">, \"title\": <string, <= 120 chars>, \"body\": <string, why it matters and what to do>, \"suggestion\": <string, OPTIONAL: replacement text for exactly lines startLine..endLine, ready to commit>}], \"concerns\": <array, OPTIONAL: [{\"severity\": <\"blocking\" | \"important\" | \"nit\">, \"title\": <string, <= 120 chars>, \"body\": <string>}], only for step-5 concerns with no valid anchor — never duplicate a finding here>, \"walkthrough\": <array, OPTIONAL: [{\"path\": <string, a changed file path>, \"summary\": <string, the step-6 sentence>}], one entry per reviewed file>}.",
502
551
  `Report at most ${maxFindings} findings and at most 10 concerns; prefer the most important ones. An empty findings array with verdict "approve" is a valid review. Include "suggestion" only when you are confident the replacement compiles and preserves intent; its text must contain the full replacement for every line in the range and nothing else.`,
503
552
  "Use verdict \"request-changes\" only when at least one finding or concern is \"blocking\". Line anchors you invent will be discarded, so copy R-numbers from read_file_diff output."
@@ -535,19 +584,70 @@ const PullRequestReviewer = Agent.define("pr-reviewer", {
535
584
  const MAX_REVIEW_UNITS = 8;
536
585
  /** A unit never carries more files than this, regardless of their size. */
537
586
  const MAX_UNIT_FILES = 12;
538
- /** Soft changed-line budget per unit; a single oversized file still gets its own unit. */
587
+ /** Compatibility export; complete evidence chars now own unit packing. */
539
588
  const UNIT_CHANGED_LINE_BUDGET = 800;
540
- /** Flat per-file cost so many tiny files still spread across units. */
541
- const FILE_OVERHEAD_LINES = 20;
589
+ /**
590
+ * Bound the complete model-visible evidence assigned to one child. This is a
591
+ * character bound rather than a token estimate because it is deterministic,
592
+ * provider-independent, and enforced before any model call.
593
+ */
594
+ const UNIT_EVIDENCE_CHAR_BUDGET = 24e4;
595
+ /** Maximum complete evidence shards placed in one child brief. */
596
+ const MAX_UNIT_EVIDENCE_SHARDS = 12;
597
+ /**
598
+ * Keep overflow diagnostics bounded to one plan's total assignment capacity.
599
+ * The plan separately records the exact overflow count and every affected
600
+ * path, so identifiers are a deterministic diagnostic sample rather than the
601
+ * authority for whether input coverage is complete.
602
+ */
603
+ const MAX_REPORTED_UNASSIGNED_EVIDENCE_SHARDS = 96;
604
+ /** @deprecated Use `MAX_PATCH_CHARS`; this is now the per-shard bound. */
605
+ const MAX_FILE_EVIDENCE_CHARS = MAX_PATCH_CHARS;
542
606
  /** The merged review never exceeds the `CodeReview` findings bound. */
543
607
  const MAX_MERGED_FINDINGS = 20;
544
608
  const ReviewUnitId = Schema.NonEmptyString.check(Schema.isMaxLength(32));
609
+ /** High-risk surfaces that receive an explicit specialist focus label. */
610
+ const ReviewRiskCategory = Schema.Literals([
611
+ "authentication-authorization",
612
+ "security-boundary",
613
+ "persistence-durability",
614
+ "concurrency",
615
+ "credential-handling",
616
+ "external-side-effects"
617
+ ]);
618
+ const ReviewDiscoveryPerspective = Schema.Literals(["general", "risk-specialist"]);
619
+ const ReviewPassId = Schema.NonEmptyString.check(Schema.isMaxLength(64));
620
+ const ReviewEvidenceShardId = Schema.NonEmptyString.check(Schema.isMaxLength(32));
621
+ /** One complete bounded slice of a changed path's model-visible evidence. */
622
+ var ReviewEvidenceShard = class extends Schema.Class("@effect-agent/pr-review/ReviewEvidenceShard")({
623
+ shardId: ReviewEvidenceShardId,
624
+ path: ChangedPath,
625
+ ordinal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
626
+ total: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
627
+ evidenceChars: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(MAX_PATCH_CHARS))
628
+ }) {};
629
+ const EvidenceShardIds$1 = Schema.Array(ReviewEvidenceShardId).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12));
630
+ /** One required, independently scoped discovery attempt. */
631
+ var ReviewDiscoveryPass = class extends Schema.Class("@effect-agent/pr-review/ReviewDiscoveryPass")({
632
+ passId: ReviewPassId,
633
+ unitId: ReviewUnitId,
634
+ paths: Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12)),
635
+ evidenceShardIds: EvidenceShardIds$1,
636
+ perspective: ReviewDiscoveryPerspective,
637
+ /** Empty for the general pass; explicit deterministic focus for specialists. */
638
+ riskCategories: Schema.Array(ReviewRiskCategory).check(Schema.isMaxLength(6))
639
+ }) {};
545
640
  /** One bounded slice of the changeset delegated to one child reviewer. */
546
641
  var ReviewUnit = class extends Schema.Class("@effect-agent/pr-review/ReviewUnit")({
547
642
  unitId: ReviewUnitId,
548
643
  paths: Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12)),
644
+ evidenceShards: Schema.Array(ReviewEvidenceShard).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12)),
549
645
  /** additions + deletions across the unit's files, for honest sizing. */
550
- changedLines: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
646
+ changedLines: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
647
+ /** Complete model-visible diff/content evidence assigned to each child. */
648
+ evidenceChars: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(UNIT_EVIDENCE_CHAR_BUDGET)),
649
+ /** Host-classified focus labels for the unit's redundant specialist pass. */
650
+ riskCategories: Schema.Array(ReviewRiskCategory).check(Schema.isMaxLength(6))
551
651
  }) {};
552
652
  /** The complete deterministic fan-out plan over one changeset. */
553
653
  var ReviewUnitPlan = class extends Schema.Class("@effect-agent/pr-review/ReviewUnitPlan")({
@@ -555,8 +655,16 @@ var ReviewUnitPlan = class extends Schema.Class("@effect-agent/pr-review/ReviewU
555
655
  /** True when the source returned fewer files than the pull request has. */
556
656
  truncated: Schema.Boolean,
557
657
  units: Schema.Array(ReviewUnit).check(Schema.isMaxLength(8)),
658
+ /** Exact discovery calls the coordinator must make. */
659
+ discoveryPasses: Schema.Array(ReviewDiscoveryPass).check(Schema.isMaxLength(16)),
558
660
  /** Changed files with neither a textual diff nor bounded base/head text. */
559
661
  undiffablePaths: Schema.Array(ChangedPath).check(Schema.isMaxLength(300)),
662
+ /** Assigned paths with one or more evidence shards beyond plan capacity. */
663
+ partialEvidencePaths: Schema.Array(ChangedPath).check(Schema.isMaxLength(300)),
664
+ /** Exact number of shards beyond the bounded unit capacity. */
665
+ unassignedEvidenceShardCount: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
666
+ /** Bounded deterministic prefix of the unassigned shard identifiers. */
667
+ unassignedEvidenceShardIds: Schema.Array(ReviewEvidenceShardId).check(Schema.isMaxLength(96)),
560
668
  /**
561
669
  * Diffable files beyond the fan-out capacity (MAX_REVIEW_UNITS units of
562
670
  * MAX_UNIT_FILES files). Never silently dropped: the coordinator must name
@@ -564,62 +672,232 @@ var ReviewUnitPlan = class extends Schema.Class("@effect-agent/pr-review/ReviewU
564
672
  */
565
673
  unassignedPaths: Schema.Array(ChangedPath).check(Schema.isMaxLength(300))
566
674
  }) {};
567
- const fileCost = (file) => {
568
- const contentChars = (file.reviewBaseContent?.length ?? 0) + (file.reviewHeadContent?.length ?? 0);
569
- const contentWeight = Math.ceil(contentChars / 200);
570
- return file.additions + file.deletions + contentWeight + FILE_OVERHEAD_LINES;
675
+ const riskRules = [
676
+ {
677
+ category: "authentication-authorization",
678
+ patterns: [
679
+ /auth/,
680
+ /authoriz/,
681
+ /permission/,
682
+ /principal/,
683
+ /access[-_ ]?control/,
684
+ /role\b/
685
+ ]
686
+ },
687
+ {
688
+ category: "security-boundary",
689
+ patterns: [
690
+ /security/,
691
+ /sandbox/,
692
+ /untrusted/,
693
+ /schema\.decode/,
694
+ /validation/,
695
+ /injection/,
696
+ /csrf/,
697
+ /xss/,
698
+ /path traversal/
699
+ ]
700
+ },
701
+ {
702
+ category: "persistence-durability",
703
+ patterns: [
704
+ /durab/,
705
+ /persist/,
706
+ /storage/,
707
+ /database/,
708
+ /\bsql\b/,
709
+ /journal/,
710
+ /ledger/,
711
+ /checkpoint/,
712
+ /migration/,
713
+ /transaction/
714
+ ]
715
+ },
716
+ {
717
+ category: "concurrency",
718
+ patterns: [
719
+ /concurr/,
720
+ /semaphore/,
721
+ /\bfiber/,
722
+ /race/,
723
+ /mutex/,
724
+ /\block\b/,
725
+ /queue/,
726
+ /parallel/,
727
+ /interrupt/
728
+ ]
729
+ },
730
+ {
731
+ category: "credential-handling",
732
+ patterns: [
733
+ /credential/,
734
+ /secret/,
735
+ /password/,
736
+ /api[-_ ]?key/,
737
+ /bearer/,
738
+ /hmac/,
739
+ /signature/
740
+ ]
741
+ },
742
+ {
743
+ category: "external-side-effects",
744
+ patterns: [
745
+ /publish/,
746
+ /webhook/,
747
+ /github/,
748
+ /fetch\(/,
749
+ /http/,
750
+ /send[-_ ]?(email|message)/,
751
+ /write[-_ ]?(file|record)/,
752
+ /delete/,
753
+ /mutation/,
754
+ /side[-_ ]?effect/,
755
+ /spawn/,
756
+ /exec/
757
+ ]
758
+ }
759
+ ];
760
+ /**
761
+ * Deterministic host policy for specialist assignment. It intentionally
762
+ * favors false positives: an extra bounded pass costs work, while a missed
763
+ * high-risk classification removes redundancy. This is not a claim that the
764
+ * keyword policy recognizes every semantically risky change.
765
+ */
766
+ const classifyReviewRisks = (file) => {
767
+ const text = [
768
+ file.path,
769
+ file.previousPath ?? "",
770
+ file.patch ?? "",
771
+ file.reviewBaseContent ?? "",
772
+ file.reviewHeadContent ?? ""
773
+ ].join("\n").toLowerCase();
774
+ return riskRules.filter((rule) => rule.patterns.some((pattern) => pattern.test(text))).map((rule) => rule.category);
775
+ };
776
+ /**
777
+ * Whether every claimed finding anchor was present in the exact bounded
778
+ * evidence shards assigned to one unit. This is stricter than checking the
779
+ * full pull-request diff when an oversized path spans multiple units.
780
+ */
781
+ const findingAnchorInUnitEvidence = (finding, unit, files) => {
782
+ const file = files.find((candidate) => candidate.path === finding.path);
783
+ if (file?.patch === void 0 || finding.endLine < finding.startLine) return false;
784
+ const assignedOrdinals = new Set(unit.evidenceShards.filter((shard) => shard.path === finding.path).map((shard) => shard.ordinal));
785
+ const visibleLines = /* @__PURE__ */ new Set();
786
+ const chunks = fileReviewEvidenceChunks(file);
787
+ for (let index = 0; index < chunks.length; index += 1) {
788
+ if (!assignedOrdinals.has(index + 1)) continue;
789
+ for (const line of chunks[index]?.annotatedPatch.split("\n") ?? []) {
790
+ const match = /^R(\d+) /.exec(line);
791
+ if (match?.[1] !== void 0) visibleLines.add(Number(match[1]));
792
+ }
793
+ }
794
+ for (let line = finding.startLine; line <= finding.endLine; line += 1) if (!visibleLines.has(line)) return false;
795
+ return true;
796
+ };
797
+ const uniquePaths = (shards) => [...new Set(shards.map(({ shard }) => shard.path))];
798
+ const plannedEvidenceShards = (files) => {
799
+ const planned = [];
800
+ let shardIndex = 0;
801
+ for (const file of files) {
802
+ const chunks = fileReviewEvidenceChunks(file);
803
+ for (let index = 0; index < chunks.length; index += 1) {
804
+ const chunk = chunks[index];
805
+ if (chunk === void 0) continue;
806
+ shardIndex += 1;
807
+ planned.push({
808
+ shard: ReviewEvidenceShard.make({
809
+ shardId: `shard-${String(shardIndex).padStart(4, "0")}`,
810
+ path: file.path,
811
+ ordinal: index + 1,
812
+ total: chunks.length,
813
+ evidenceChars: chunk.annotatedPatch.length
814
+ }),
815
+ file,
816
+ changedLines: index === 0 ? file.additions + file.deletions : 0
817
+ });
818
+ }
819
+ }
820
+ return planned;
571
821
  };
572
- const unitOf = (index, files) => ReviewUnit.make({
822
+ const unitOf = (index, shards) => ReviewUnit.make({
573
823
  unitId: `unit-${String(index + 1).padStart(3, "0")}`,
574
- paths: files.map((file) => file.path),
575
- changedLines: files.reduce((total, file) => total + file.additions + file.deletions, 0)
824
+ paths: uniquePaths(shards),
825
+ evidenceShards: shards.map(({ shard }) => shard),
826
+ changedLines: shards.reduce((total, shard) => total + shard.changedLines, 0),
827
+ evidenceChars: shards.reduce((total, { shard }) => total + shard.evidenceChars, 0),
828
+ riskCategories: [...new Set(shards.flatMap(({ file }) => classifyReviewRisks(file)))]
576
829
  });
830
+ const discoveryPassesFor = (units) => units.flatMap((unit) => [ReviewDiscoveryPass.make({
831
+ passId: `${unit.unitId}-general`,
832
+ unitId: unit.unitId,
833
+ paths: unit.paths,
834
+ evidenceShardIds: unit.evidenceShards.map((shard) => shard.shardId),
835
+ perspective: "general",
836
+ riskCategories: []
837
+ }), ReviewDiscoveryPass.make({
838
+ passId: `${unit.unitId}-specialist`,
839
+ unitId: unit.unitId,
840
+ paths: unit.paths,
841
+ evidenceShardIds: unit.evidenceShards.map((shard) => shard.shardId),
842
+ perspective: "risk-specialist",
843
+ riskCategories: unit.riskCategories
844
+ })]);
577
845
  /**
578
846
  * Group the changeset into at most `MAX_REVIEW_UNITS` review units.
579
847
  *
580
848
  * Deterministic by construction: files are ordered by path (so files sharing
581
849
  * a directory become neighbors — directory affinity without a heuristic),
582
- * then packed greedily in that order under the soft changed-line budget and
583
- * the hard per-unit file bound. Capacity is finite and explicit:
850
+ * then split into complete line-bounded evidence shards and packed greedily
851
+ * under the hard evidence and per-unit shard bounds. Capacity is finite and
852
+ * explicit:
584
853
  *
585
854
  * - files without a textual diff are still delegated when the source
586
855
  * recovered complete bounded UTF-8 base/head content. Findings from that
587
856
  * evidence cannot anchor inline and are reported as concerns;
588
857
  * - files with neither form of textual evidence surface in
589
858
  * `undiffablePaths` instead of laundering missing coverage;
590
- * - reviewable files beyond `MAX_REVIEW_UNITS` full units surface in
591
- * `unassignedPaths` so the review can report them as unreviewed, never
592
- * silently truncated.
859
+ * - an oversized path spans as many deterministic shards and units as needed;
860
+ * - shards beyond `MAX_REVIEW_UNITS` full units surface explicitly, so a path
861
+ * is partial only when finite plan capacity is genuinely exhausted.
593
862
  */
594
863
  const planReviewUnits = (files, options) => {
595
864
  const ordered = [...files].sort((left, right) => left.path < right.path ? -1 : 1);
596
865
  const reviewable = ordered.filter(isReviewableFile);
597
866
  const undiffable = ordered.filter((file) => !isReviewableFile(file));
867
+ const shards = plannedEvidenceShards(reviewable);
598
868
  const groups = [];
599
869
  const unassigned = [];
600
870
  let current = [];
601
- let currentCost = 0;
602
- for (const file of reviewable) {
603
- const cost = fileCost(file);
604
- if (current.length >= 12 || current.length > 0 && currentCost + cost > 800) {
871
+ let currentEvidenceChars = 0;
872
+ for (const shard of shards) {
873
+ const nextPaths = /* @__PURE__ */ new Set([...uniquePaths(current), shard.shard.path]);
874
+ if (current.length >= 12 || nextPaths.size > 12 || current.length > 0 && currentEvidenceChars + shard.shard.evidenceChars > 24e4) {
605
875
  groups.push(current);
606
876
  current = [];
607
- currentCost = 0;
877
+ currentEvidenceChars = 0;
608
878
  }
609
879
  if (groups.length >= 8) {
610
- unassigned.push(file);
880
+ unassigned.push(shard);
611
881
  continue;
612
882
  }
613
- current.push(file);
614
- currentCost += cost;
883
+ current.push(shard);
884
+ currentEvidenceChars += shard.shard.evidenceChars;
615
885
  }
616
886
  if (current.length > 0 && groups.length < 8) groups.push(current);
887
+ const units = groups.map((group, index) => unitOf(index, group));
888
+ const assignedShardIds = new Set(units.flatMap((unit) => unit.evidenceShards.map((shard) => shard.shardId)));
889
+ const assignedPaths = new Set(shards.filter(({ shard }) => assignedShardIds.has(shard.shardId)).map(({ shard }) => shard.path));
890
+ const unassignedPathsWithEvidence = new Set(unassigned.map(({ shard }) => shard.path));
617
891
  return ReviewUnitPlan.make({
618
892
  totalFiles: files.length,
619
893
  truncated: files.length < options.totalChangedFiles,
620
- units: groups.map((group, index) => unitOf(index, group)),
894
+ units,
895
+ discoveryPasses: discoveryPassesFor(units),
621
896
  undiffablePaths: undiffable.map((file) => file.path),
622
- unassignedPaths: unassigned.map((file) => file.path)
897
+ partialEvidencePaths: [...unassignedPathsWithEvidence].filter((path) => assignedPaths.has(path)),
898
+ unassignedEvidenceShardCount: unassigned.length,
899
+ unassignedEvidenceShardIds: unassigned.slice(0, 96).map(({ shard }) => shard.shardId),
900
+ unassignedPaths: [...unassignedPathsWithEvidence].filter((path) => !assignedPaths.has(path))
623
901
  });
624
902
  };
625
903
  const severityRank = {
@@ -652,116 +930,311 @@ const rankAndDedupeFindings = (findings) => {
652
930
  };
653
931
  //#endregion
654
932
  //#region src/internal/fan-out.ts
655
- /** One child returns at most this many findings; the merge caps the total. */
656
- const MAX_CHILD_FINDINGS = 8;
657
- /** One child returns at most this many non-anchored concerns. */
933
+ /** One discovery pass returns at most this many anchored candidates. */
934
+ const MAX_CHILD_FINDINGS = 6;
935
+ /** One discovery pass returns at most this many non-anchored candidates. */
658
936
  const MAX_CHILD_CONCERNS = 3;
937
+ /** Every unit receives independent general and specialist discovery passes. */
938
+ const MAX_UNIT_CANDIDATES = 18;
939
+ /** General + specialist discovery for every unit, then one verifier per unit. */
940
+ const MAX_REVIEW_CHILDREN = 24;
941
+ /** Structural minimum for a child that exposes no tools. */
942
+ const MAX_FILE_REVIEW_TOOL_CALLS = 1;
943
+ const ReviewWorkPhase = Schema.Literals(["discovery", "verification"]);
944
+ const ReviewWorkPerspective = Schema.Literals([
945
+ "general",
946
+ "risk-specialist",
947
+ "candidate-verification"
948
+ ]);
949
+ const ReviewCandidateId = Schema.NonEmptyString.check(Schema.isMaxLength(96));
950
+ var FindingCandidate = class extends Schema.TaggedClass()("FindingCandidate", {
951
+ candidateId: ReviewCandidateId,
952
+ workId: ReviewPassId,
953
+ unitId: ReviewUnitId,
954
+ finding: ReviewFinding,
955
+ evidencePaths: Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(Schema.isMaxLength(1))
956
+ }) {};
957
+ var ConcernCandidate = class extends Schema.TaggedClass()("ConcernCandidate", {
958
+ candidateId: ReviewCandidateId,
959
+ workId: ReviewPassId,
960
+ unitId: ReviewUnitId,
961
+ concern: ReviewConcern,
962
+ evidencePaths: Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(Schema.isMaxLength(3))
963
+ }) {};
964
+ const ReviewCandidate = Schema.Union([FindingCandidate, ConcernCandidate]);
965
+ /** Deterministic host equivalence for claims repeated across discovery passes. */
966
+ const reviewCandidateSubjectKey = (candidate) => candidate._tag === "FindingCandidate" ? `finding:${JSON.stringify(Schema.encodeSync(ReviewFinding)(candidate.finding))}` : `concern:${JSON.stringify(Schema.encodeSync(ReviewConcern)(candidate.concern))}`;
967
+ var CandidateAssessment = class extends Schema.Class("@effect-agent/pr-review/CandidateAssessment")({
968
+ candidateId: ReviewCandidateId,
969
+ disposition: Schema.Literals(["confirmed", "rejected"]),
970
+ rationale: Schema.NonEmptyString.check(Schema.isMaxLength(600))
971
+ }) {};
659
972
  /**
660
- * One mandatory diff read plus one bounded context read for every path in a
661
- * maximum-size unit. Keep the child and delegation reservation aligned.
973
+ * Concern candidates need explicit paths internally to bind the claim to
974
+ * scheduled evidence. The verifier receives the complete bounded unit so it
975
+ * can use neighboring evidence to falsify the claim. The public ReviewConcern
976
+ * remains path-free after the host confirms and projects it.
662
977
  */
663
- const MAX_FILE_REVIEW_TOOL_CALLS = 24;
664
- const FileReviewToolkit = Toolkit.make(ReadFileDiff, ReadFile);
665
- const FileReviewToolkitLayer = FileReviewToolkit.toLayer({
666
- read_file_diff: readFileDiffHandler,
667
- read_file: readFileHandler
668
- });
978
+ var DiscoveredConcern = class extends Schema.Class("@effect-agent/pr-review/DiscoveredConcern")({
979
+ concern: ReviewConcern,
980
+ evidencePaths: Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(Schema.isMaxLength(3))
981
+ }) {};
669
982
  const UnitPaths = Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12));
670
- /** The child Agent input: one briefed unit of the changeset. */
983
+ const RiskCategories = Schema.Array(ReviewRiskCategory).check(Schema.isMaxLength(6));
984
+ const Candidates = Schema.Array(ReviewCandidate).check(Schema.isMaxLength(18));
985
+ 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
+ /** One complete host-selected evidence shard supplied to a review child. */
999
+ var FileReviewEvidence = class extends Schema.Class("@effect-agent/pr-review/FileReviewEvidence")({
1000
+ shardId: ReviewEvidenceShardId,
1001
+ path: ChangedPath,
1002
+ status: ChangedFileStatus,
1003
+ reviewMode: Schema.Literals([
1004
+ "diff",
1005
+ "content",
1006
+ "unavailable"
1007
+ ]),
1008
+ ordinal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
1009
+ total: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
1010
+ annotatedPatch: Schema.String.check(Schema.isMaxLength(MAX_PATCH_CHARS))
1011
+ }) {};
1012
+ /** Host-prepared child input with complete bounded diff/content evidence. */
671
1013
  var FileReviewBrief = class extends Schema.Class("@effect-agent/pr-review/FileReviewBrief")({
1014
+ phase: ReviewWorkPhase,
1015
+ workId: ReviewPassId,
672
1016
  unitId: ReviewUnitId,
673
1017
  paths: UnitPaths,
674
- focus: Schema.NonEmptyString.check(Schema.isMaxLength(200))
1018
+ evidenceShardIds: EvidenceShardIds,
1019
+ perspective: ReviewWorkPerspective,
1020
+ riskCategories: RiskCategories,
1021
+ candidates: Candidates,
1022
+ evidence: Schema.Array(FileReviewEvidence).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12))
675
1023
  }) {};
676
- /** The child Agent output: the briefed unit's bounded findings and concerns. */
1024
+ /** Child output; phase-inapplicable collections must be empty. */
677
1025
  var FileReviewReport = class extends Schema.Class("@effect-agent/pr-review/FileReviewReport")({
1026
+ phase: ReviewWorkPhase,
1027
+ workId: ReviewPassId,
678
1028
  unitId: ReviewUnitId,
679
- findings: Schema.Array(ReviewFinding).check(Schema.isMaxLength(8)),
680
- /** Unit-scoped concerns with no diff line to anchor to. */
681
- concerns: Schema.optionalKey(Schema.Array(ReviewConcern).check(Schema.isMaxLength(3))),
682
- /** One-sentence per-file change summaries for the merged walkthrough. */
683
- fileSummaries: Schema.optionalKey(Schema.Array(WalkthroughEntry).check(Schema.isMaxLength(12)))
1029
+ findings: Schema.Array(ReviewFinding).check(Schema.isMaxLength(6)),
1030
+ concerns: Schema.Array(DiscoveredConcern).check(Schema.isMaxLength(3)),
1031
+ fileSummaries: Schema.Array(WalkthroughEntry).check(Schema.isMaxLength(12)),
1032
+ assessments: Schema.Array(CandidateAssessment).check(Schema.isMaxLength(18))
1033
+ }) {};
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", {
1048
+ workId: ReviewPassId,
1049
+ reason: Schema.NonEmptyString.check(Schema.isMaxLength(600))
684
1050
  }) {};
1051
+ const FileReviewFailure = Schema.Union([FileReviewUnitFailed, FileReviewWorkRejected]);
685
1052
  const staticGuidanceLines = (guidance) => {
686
1053
  if (guidance === void 0) return [];
687
1054
  return (typeof guidance === "string" ? [guidance] : guidance).filter((line) => line.length > 0);
688
1055
  };
689
- /** Build the child file-reviewer instructions with optional static guidance. */
690
- const makeFileReviewerInstructions = (options = {}) => (brief) => [
691
- `You are a code reviewer for one unit of a pull request: unit ${brief.unitId}, covering exactly these changed files: ${brief.paths.join(", ")}. Focus: ${brief.focus}.`,
692
- ...staticGuidanceLines(options.guidance),
693
- "Work in this order:",
694
- "1. Call read_file_diff for every file in your unit. A normal diff marks new-version anchors as R<number>; only those numbers are valid startLine/endLine values. When GitHub omitted a diff, the tool may return bounded base/head content marked B/H instead. Review that content, but report its defects as non-anchored concerns because B/H lines cannot anchor GitHub comments. Never anchor a finding to a removed (-), B, or H line.",
695
- "2. Call read_file when you need surrounding context the diff does not show. ONLY files in the changeset are readable: a request for any other path (an import, a neighbor, a config) returns a failed result — do not retry it; reason from the diff instead and note the gap in your report when it matters.",
696
- "3. Review for real defects first: correctness, security, concurrency, resource leaks, error handling, API misuse. Style nits are least important. Do not praise; do not restate the diff.",
697
- "When the diff adds or changes a test, check that it can actually fail: a test that would still pass with the bug present is theatre, not coverage. The usual tell is a loose assertion standing where an exact one belongs — >= or a truthiness check over an expected value, or a snapshot that absorbs whatever it is handed.",
698
- "Drop bloat-shaped findings before reporting: defensive checks for cases that cannot happen, abstractions used once, comments restating obvious code, tests asserting tautologies, just-in-case guards. A finding must be sound, correct, and worth acting on; prefer an explicit keep over an invented finding.",
699
- `4. For every file in your unit, write one factual sentence (<= 240 chars) describing what changed in that file — for a reader scanning the pull request, never a line-by-line restatement.`,
700
- `5. Then return ONLY a JSON object — no Markdown fences, no prose before or after — exactly this shape: {"unitId": ${JSON.stringify(brief.unitId)}, "findings": [{"path": <string, a file in your unit>, "startLine": <integer, an R-marked new-file line>, "endLine": <integer, >= startLine, same file, R-marked>, "severity": <"blocking" | "important" | "nit">, "category": <OPTIONAL: "correctness" | "security" | "concurrency" | "performance" | "resources" | "error-handling" | "testing" | "maintainability" | "style" | "docs">, "title": <string, <= 120 chars>, "body": <string, why it matters and what to do>, "suggestion": <string, OPTIONAL: replacement text for exactly lines startLine..endLine, ready to commit>}], "concerns": <array, OPTIONAL: [{"severity": <"blocking" | "important" | "nit">, "title": <string, <= 120 chars>, "body": <string>}], only for concerns about YOUR unit's files with no valid line anchor (a missing cleanup, a coverage gap the diff implies, sequencing the diff leaves open) — never duplicate a finding here>, "fileSummaries": <array, OPTIONAL: [{"path": <string, a file in your unit>, "summary": <string, the step-4 sentence>}], one entry per file in your unit>}.`,
701
- `Report at most 8 findings and at most 3 concerns; prefer the most important ones. An empty findings array is a valid report. Never report on files outside your unit. Line anchors you invent will be discarded, so copy R-numbers from read_file_diff output.`
702
- ].join("\n");
1056
+ const evidenceInstructions = [
1057
+ "The host placed complete bounded review evidence shards in the input evidence array. Treat every shard as required input; ordinal/total identifies multi-shard paths.",
1058
+ "You have no tools and cannot roam outside this evidence. If it is insufficient for a candidate, reject or omit that candidate rather than guessing.",
1059
+ "A diff marks new-version anchors as R<number>; only those lines may anchor findings. B/H content evidence is non-anchorable."
1060
+ ];
1061
+ /** Discovery and verification instructions share one child definition. */
1062
+ const makeFileReviewerInstructions = (options = {}) => (brief) => {
1063
+ const common = [
1064
+ `You are an attached review worker for ${brief.workId} in host-planned unit ${brief.unitId}: ${brief.paths.join(", ")}.`,
1065
+ ...staticGuidanceLines(options.guidance),
1066
+ ...evidenceInstructions
1067
+ ];
1068
+ if (brief.phase === "verification") return [
1069
+ ...common,
1070
+ "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
+ "The evidence array contains the complete bounded unit, including neighboring changed code that may confirm or falsify a locally plausible claim.",
1072
+ "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."
1074
+ ].join("\n");
1075
+ 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
+ return [
1077
+ ...common,
1078
+ focus,
1079
+ "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
+ "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.`
1082
+ ].join("\n");
1083
+ };
703
1084
  const fileReviewerInstructions = makeFileReviewerInstructions();
704
- /** The default per-unit child execution bounds. */
1085
+ const FileReviewToolkit = Toolkit.empty;
1086
+ /** Compatibility export: the evidence-only child has no handler requirements. */
1087
+ const FileReviewToolkitLayer = Layer.empty;
705
1088
  const defaultFileReviewerPolicy = AgentPolicy.make({
706
- maxTurns: 8,
707
- maxToolCalls: MAX_FILE_REVIEW_TOOL_CALLS,
1089
+ maxTurns: 6,
1090
+ maxToolCalls: 1,
708
1091
  maxDuration: "6 minutes",
709
1092
  toolConcurrency: 2,
710
- repeatedFailureLimit: 12,
1093
+ repeatedFailureLimit: 6,
711
1094
  tokenBudget: 2e5,
712
1095
  contextTokenLimit: 15e4,
713
1096
  toolResultBounds: ToolResultBounds.make({ maxBytes: REVIEW_TOOL_RESULT_MAX_BYTES }),
714
1097
  onExhaustion: "fail"
715
1098
  });
716
- /** The model-decoded delegation parameters: which unit to review. */
717
- var FileReviewRequest = class extends Schema.Class("@effect-agent/pr-review/FileReviewRequest")({
718
- unitId: ReviewUnitId,
719
- paths: UnitPaths
720
- }) {};
721
- /** The bounded parent-visible result of one delegated unit review. */
722
- var FileReviewUnitResult = class extends Schema.Class("@effect-agent/pr-review/FileReviewUnitResult")({
723
- unitId: ReviewUnitId,
724
- findings: Schema.Array(ReviewFinding).check(Schema.isMaxLength(8)),
725
- /** Unit-scoped concerns with no diff line to anchor to. */
726
- concerns: Schema.optionalKey(Schema.Array(ReviewConcern).check(Schema.isMaxLength(3))),
727
- /** One-sentence per-file change summaries for the merged walkthrough. */
728
- fileSummaries: Schema.optionalKey(Schema.Array(WalkthroughEntry).check(Schema.isMaxLength(12)))
729
- }) {};
730
- /**
731
- * One unit's review failed: the child Run ended in a typed failure (policy
732
- * bound, output violation, model fault). The marker is bounded and carries no
733
- * child transcript content beyond the failure tag and message.
734
- */
735
- var FileReviewUnitFailed = class extends Schema.TaggedError()("FileReviewUnitFailed", {
736
- childErrorTag: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
737
- message: Schema.String.check(Schema.isMaxLength(400))
738
- }) {};
739
- /**
740
- * Finite per-invocation bounds (SUB-009), aligned with the child's own
741
- * AgentPolicy: the child's policy is the limit that trips typed; the
742
- * reservation mirrors it so parent-side accounting stays honest.
743
- */
744
1099
  const fileReviewPolicy = SubagentPolicy.make({
745
- maxChildren: 8,
746
- maxConcurrency: 3,
747
- maxTurns: 8,
748
- maxToolCalls: MAX_FILE_REVIEW_TOOL_CALLS,
749
- maxDuration: "6 minutes"
1100
+ maxChildren: MAX_REVIEW_CHILDREN,
1101
+ maxConcurrency: 4,
1102
+ maxTurns: 6,
1103
+ maxToolCalls: 1,
1104
+ maxDuration: "6 minutes",
1105
+ maxResultBytes: 256 * 1024
750
1106
  });
751
- const delegationDescription = "Delegate the review of one planned unit to a bounded file-reviewer child and return its line-anchored findings. Call it exactly once per unit from list_review_units; never retry a failed unit.";
752
- /**
753
- * Total mapping from every expected child Run failure to the declared unit
754
- * failure (SUB-028): the tag plus a bounded message, nothing else crosses.
755
- */
756
1107
  const mapFileReviewChildFailure = (failure) => FileReviewUnitFailed.make({
757
1108
  childErrorTag: failure._tag,
758
1109
  message: (failure.message ?? "").slice(0, 400)
759
1110
  });
760
- var ListReviewUnitsQuery = class extends Schema.Class("@effect-agent/pr-review/ListReviewUnitsQuery")({
761
- /** Explicit constant keeps the zero-choice operation compatible with strict provider schemas. */
762
- scope: Schema.Literal("all") }) {};
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
+ }
1138
+ }
1139
+ const byPath = new Map(files.map((file) => [file.path, file]));
1140
+ const evidence = [];
1141
+ for (const shard of unit.evidenceShards) {
1142
+ 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}`);
1147
+ evidence.push(FileReviewEvidence.make({
1148
+ shardId: shard.shardId,
1149
+ path: shard.path,
1150
+ status: file.status,
1151
+ reviewMode: chunk.reviewMode,
1152
+ ordinal: shard.ordinal,
1153
+ total: shard.total,
1154
+ annotatedPatch: chunk.annotatedPatch
1155
+ }));
1156
+ }
1157
+ return FileReviewBrief.make({
1158
+ ...request,
1159
+ evidence
1160
+ });
1161
+ });
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);
1173
+ }
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
+ }));
1183
+ }
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}`);
1198
+ }
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,
1203
+ finding,
1204
+ 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,
1210
+ concern: candidate.concern,
1211
+ 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") }) {};
763
1236
  const ListReviewUnits = Tool.make("list_review_units", {
764
- description: "List this pull request's changeset grouped into bounded review units (size-budgeted, directory-affine), plus the files no unit can cover.",
1237
+ description: "List deterministic bounded review units, explicit risk categories, every required discovery pass, and paths the pipeline cannot cover.",
765
1238
  parameters: ListReviewUnitsQuery,
766
1239
  success: ReviewUnitPlan,
767
1240
  failure: PullRequestSourceFailure,
@@ -773,80 +1246,44 @@ const FanOutCoordinatorToolkitLayer = FanOutCoordinatorToolkit.toLayer({ list_re
773
1246
  const source = yield* PullRequestSource;
774
1247
  return planReviewUnits(yield* source.changedFiles, { totalChangedFiles: (yield* source.metadata).totalChangedFiles });
775
1248
  }) });
776
- /**
777
- * Build the coordinator's instructions. The same consumer guidance the
778
- * children receive is injected between the mission framing and the procedure
779
- * so the merged summary and verdict are shaped by the same review profile,
780
- * and the configured findings bound reaches the merge step instead of only
781
- * the host-side trim.
782
- */
783
1249
  const makeFanOutReviewInstructions = (options = {}) => (mission) => {
784
1250
  const maxFindings = clampMaxFindings(options.maxFindings);
785
1251
  return [
786
- `You coordinate the review of pull request #${mission.number} ("${mission.title}") in ${mission.repository}, merging ${mission.headRef} into ${mission.baseRef}. It changes ${mission.changedFileCount} file(s).`,
787
- mission.body.length > 0 ? `Author description:\n${mission.body}` : "The author provided no description.",
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.",
788
1254
  ...staticGuidanceLines(options.guidance),
789
- "Work in this order:",
790
- "1. Call list_review_units once to get the planned review units.",
791
- "2. Call delegate_file_review EXACTLY once per unit, passing each unit's unitId and paths verbatim. Prefer declaring all delegation calls in one batch. Never review files yourself and never invent units.",
792
- "3. A delegation result with \"_tag\" is a FAILED unit. Never retry it; instead your summary MUST name it honestly, e.g. \"unit-002 unreviewed: AgentPolicyError\". The plan's undiffablePaths and unassignedPaths must also be named as not reviewed when present.",
793
- `4. Merge the successful units' findings: drop duplicates sharing the same path and line range keeping the most severe, rank blocking > important > nit, and keep at most ${maxFindings} findings. Drop bloat-shaped findings during the merge defensive checks for cases that cannot happen, abstractions used once, comments restating obvious code, tests asserting tautologies; children bias toward recommending changes, and a finding must be sound, correct, and worth acting on to survive.`,
794
- `5. Merge the units' concerns the same way: drop duplicates keeping the most severe, and keep at most 10.`,
795
- "6. Merge the units' fileSummaries into one walkthrough: copy each entry verbatim, one entry per file, dropping duplicate paths.",
796
- "7. Then return ONLY a JSON object — no Markdown fences, no prose before or after — exactly this shape: {\"summary\": <string, 1-3 paragraphs of overall assessment, including every unreviewed unit or file>, \"verdict\": <\"approve\" | \"comment\" | \"request-changes\">, \"findings\": [{\"path\": <string>, \"startLine\": <integer>, \"endLine\": <integer>, \"severity\": <\"blocking\" | \"important\" | \"nit\">, \"category\": <string, OPTIONAL>, \"title\": <string, <= 120 chars>, \"body\": <string>, \"suggestion\": <string, OPTIONAL>}], \"concerns\": <array, OPTIONAL: [{\"severity\": <\"blocking\" | \"important\" | \"nit\">, \"title\": <string, <= 120 chars>, \"body\": <string>}], the merged unit concerns>, \"walkthrough\": <array, OPTIONAL: [{\"path\": <string>, \"summary\": <string>}], the merged fileSummaries>}. Copy findings (including \"category\" and \"suggestion\" when present), concerns, and walkthrough entries verbatim from the delegation results; never invent or edit anchors.",
797
- "Use verdict \"request-changes\" only when at least one finding or concern is \"blocking\". An empty findings array with verdict \"approve\" is a valid review when every unit succeeded and found nothing."
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."
798
1261
  ].join("\n");
799
1262
  };
800
1263
  const fanOutReviewInstructions = makeFanOutReviewInstructions();
801
- /** The default fan-out coordinator execution bounds. */
802
1264
  const defaultFanOutPolicy = AgentPolicy.make({
803
- maxTurns: 6,
804
- maxToolCalls: 9,
805
- maxDuration: "15 minutes",
806
- toolConcurrency: 3,
1265
+ maxTurns: 7,
1266
+ maxToolCalls: 25,
1267
+ maxDuration: "20 minutes",
1268
+ toolConcurrency: 4,
807
1269
  repeatedFailureLimit: 3,
808
- tokenBudget: 3e5,
1270
+ tokenBudget: 4e5,
809
1271
  contextTokenLimit: 15e4,
810
1272
  onExhaustion: "final-answer"
811
1273
  });
812
- const makeFileReviewerDefinition = (options = {}) => Agent.define("pr-file-reviewer", {
1274
+ const makeFileReviewerDefinition = (options = {}) => Agent.define("pr-review-worker", {
813
1275
  input: FileReviewBrief,
814
1276
  output: FileReviewReport,
815
1277
  instructions: makeFileReviewerInstructions(options),
816
1278
  toolkit: FileReviewToolkit,
817
1279
  policy: defaultFileReviewerPolicy,
818
- description: "Review one bounded unit of a pull request's changeset read-only and return line-anchored findings for exactly those files.",
1280
+ description: "Perform one bounded discovery or independent candidate-verification pass over host-supplied pull-request evidence.",
819
1281
  metadata: {
820
1282
  deploymentClass: "E",
821
- surface: "read-only"
1283
+ surface: "read-only",
1284
+ stage: "discovery-verification"
822
1285
  }
823
1286
  });
824
- const makeFileReviewDelegation = (child) => Subagent.define("delegate_file_review", {
825
- description: delegationDescription,
826
- target: child,
827
- parameters: FileReviewRequest,
828
- success: FileReviewUnitResult,
829
- failure: FileReviewUnitFailed,
830
- failureMode: "return",
831
- prepareInput: (request) => Effect.succeed(FileReviewBrief.make({
832
- unitId: request.unitId,
833
- paths: request.paths,
834
- focus: "defects-first: correctness, security, concurrency, resources, error handling"
835
- })),
836
- projectResult: (report) => Effect.succeed(FileReviewUnitResult.make({
837
- unitId: report.unitId,
838
- findings: report.findings,
839
- ...report.concerns !== void 0 ? { concerns: report.concerns } : {},
840
- ...report.fileSummaries !== void 0 ? { fileSummaries: report.fileSummaries } : {}
841
- })),
842
- policy: fileReviewPolicy
843
- });
844
- /**
845
- * The coordinator-facing delegation Tool: the delegation's own first-party
846
- * contained Tool plus the read-only execution class (the delegated child's
847
- * whole tool surface is read-only). Effect AI resolves handlers by Tool name,
848
- * so `SubagentRuntime.layer`'s handler serves this annotated copy unchanged.
849
- */
850
1287
  const delegationToolFor = (delegation) => delegation.tool.annotate(ToolExecutionClass, "readonly");
851
1288
  const makeFanOutReviewerDefinition = (options, delegation) => Agent.define("pr-fanout-reviewer", {
852
1289
  input: ReviewMission,
@@ -854,14 +1291,14 @@ const makeFanOutReviewerDefinition = (options, delegation) => Agent.define("pr-f
854
1291
  instructions: makeFanOutReviewInstructions(options),
855
1292
  toolkit: Toolkit.make(ListReviewUnits, delegationToolFor(delegation)),
856
1293
  policy: defaultFanOutPolicy,
857
- description: "Coordinate one pull-request review by fanning bounded per-unit file reviews out to delegated children and merging their findings into one structured review.",
1294
+ description: "Coordinate deterministic general/specialist discovery and independent candidate verification over bounded review units.",
858
1295
  metadata: {
859
1296
  deploymentClass: "E",
860
1297
  surface: "read-only",
861
- delegation: "S1-attached"
1298
+ delegation: "S1-attached",
1299
+ assurance: "multi-pass"
862
1300
  }
863
1301
  });
864
- /** Build one coherent fan-out suite: child, coordinator, and delegation. */
865
1302
  const makeFanOutReviewSuite = (options = {}) => {
866
1303
  const child = makeFileReviewerDefinition({ guidance: options.guidance });
867
1304
  const delegation = makeFileReviewDelegation(child);
@@ -872,25 +1309,13 @@ const makeFanOutReviewSuite = (options = {}) => {
872
1309
  };
873
1310
  };
874
1311
  const defaultSuite = makeFanOutReviewSuite();
875
- /** The default child Agent Definition. */
876
1312
  const FileReviewer = defaultSuite.child;
877
- /** The default coordinator Agent Definition. */
878
1313
  const FanOutReviewer = defaultSuite.parent;
879
- /** The default delegation over the default child. */
880
1314
  const fileReviewDelegation = defaultSuite.delegation;
881
- /** The default coordinator-facing delegation Tool (first-party contained mode). */
882
1315
  const DelegateFileReview = delegationToolFor(fileReviewDelegation);
883
- /** The default coordinator Toolkit. */
884
1316
  const FanOutReviewToolkit = FanOutReviewer.toolkit;
885
- /**
886
- * The contained failure family the delegation can surface as result data
887
- * (SUB-033), derived from the delegation itself so the coverage decoder can
888
- * never diverge from what the runtime actually contains.
889
- */
890
1317
  const FileReviewDelegationFailure = fileReviewDelegation.containedFailure;
891
- /** Runtime wiring: one delegation plus one explicit child Binding. */
892
1318
  const fanOutHandlersLayerFor = (delegation) => (childBinding) => SubagentRuntime.layer(delegation, childBinding, { mapChildFailure: mapFileReviewChildFailure });
893
- /** Runtime wiring over the default delegation, mirroring the leaf example. */
894
1319
  const fanOutHandlersLayer = fanOutHandlersLayerFor(fileReviewDelegation);
895
1320
  //#endregion
896
1321
  //#region src/internal/fingerprint.ts
@@ -957,10 +1382,12 @@ var StoredReviewConcern = class extends Schema.Class("@effect-agent/pr-review/St
957
1382
  body: StoredText
958
1383
  }) {};
959
1384
  /**
960
- * Versioned state embedded in one successfully covered review. The reviewed
961
- * head plus the full-scope fingerprint means every path not represented by an
962
- * unresolved item is accepted at that head; storing hundreds of path strings
963
- * separately would not fit GitHub's bounded review body in the worst case.
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.
964
1391
  */
965
1392
  var ReviewState = class extends Schema.Class("@effect-agent/pr-review/ReviewState")({
966
1393
  version: Schema.Literal(1),
@@ -1170,7 +1597,7 @@ const selectReviewRange = (input) => {
1170
1597
  const selectedFiles = [...selectedByPath.values()].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
1171
1598
  return {
1172
1599
  mode: "incremental",
1173
- reason: `changes since successfully reviewed head ${input.priorState.reviewedHeadSha.slice(0, 7)}${baseReason}`,
1600
+ reason: `changes since settled review head ${input.priorState.reviewedHeadSha.slice(0, 7)}${baseReason}`,
1174
1601
  files: selectedFiles,
1175
1602
  affectedPaths: [...affectedPaths].sort(),
1176
1603
  totalFiles: selectedFiles.length,
@@ -1842,6 +2269,6 @@ const fingerprintUnchanged = (current) => Effect.gen(function* () {
1842
2269
  return Option.isSome(latest) && latest.value === current;
1843
2270
  });
1844
2271
  //#endregion
1845
- export { FanOutReviewToolkit as $, ReadFile as $t, ReviewStateAuthenticator as A, isReviewableFile as An, ReviewUnit as At, selectedPullRequestSourceLayer as B, FileDiffView as Bt, MAX_REVIEW_STATE_MARKER_CHARS as C, ChangedFile as Cn, makeFanOutReviewInstructions as Ct, ReviewScopeMode as D, annotatePatch as Dn, MAX_MERGED_FINDINGS as Dt, ReviewMode as E, MAX_REVIEW_CONTENT_CHARS as En, mapFileReviewChildFailure as Et, buildProfileMission as F, rankAndDedupeFindings as Ft, webCryptoReviewStateAuthenticatorLayer as G, ListChangedFiles as Gt, toStoredFinding as H, FileSliceQuery as Ht, computeProfileFingerprint as I, ChangedFileSummary as It, extractFingerprint as J, MAX_FINDINGS as Jt, FINGERPRINT_MARKER_LENGTH as K, ListChangedFilesQuery as Kt, fromStoredConcern as L, ChangedFilesView as Lt, ReviewStateMarkerTooLarge as M, renderReviewContent as Mn, ReviewUnitPlan as Mt, StoredReviewConcern as N, UNIT_CHANGED_LINE_BUDGET as Nt, ReviewState as O, commentableLines as On, MAX_REVIEW_UNITS as Ot, StoredReviewFinding as P, planReviewUnits as Pt, FanOutCoordinatorToolkitLayer as Q, REVIEW_TOOL_RESULT_MAX_BYTES as Qt, fromStoredFinding as R, CodeReview as Rt, GitCommitSha as S, normalizeRepoRelativePath as Sn, fileReviewerInstructions as St, ReviewHeadComparison as T, ChangedPath as Tn, makeFileReviewerInstructions as Tt, unavailableReviewStateAuthenticatorLayer as U, FindingCategory as Ut, toStoredConcern as V, FileSlice as Vt, validateReviewState as W, FindingSeverity as Wt, DelegateFileReview as X, MAX_WALKTHROUGH_SUMMARY_CHARS as Xt, renderFingerprintMarker as Y, MAX_WALKTHROUGH_ENTRIES as Yt, FanOutCoordinatorToolkit as Z, PullRequestReviewer as Zt, ReviewRetirementHost as _, MAX_FILE_CHARS as _n, fanOutHandlersLayer as _t, PriorReviews as a, ReviewToolkitLayer as an, FileReviewToolkit as at, hasReviewMetadataMarker as b, PullRequestSourceFailure as bn, fileReviewDelegation as bt, fingerprintUnchanged as c, clampMaxFindings as cn, FileReviewUnitResult as ct, gitHubReviewPublisherLayer as d, makeReviewInstructions as dn, ListReviewUnitsQuery as dt, ReadFileDiff as en, FanOutReviewer as et, gitHubReviewRetirementHostLayer as f, readFileDiffHandler as fn, MAX_CHILD_CONCERNS as ft, ReviewRetirementFailure as g, MAX_CHANGED_FILES as gn, defaultFileReviewerPolicy as gt, RetirableReviewComment as h, reviewInstructions as hn, defaultFanOutPolicy as ht, PriorReviewLookupFailure as i, ReviewToolkit as in, FileReviewRequest as it, ReviewStateMarker as j, parsePatch as jn, ReviewUnitId as jt, ReviewStateAuthenticationFailure as k, hasReviewableContent as kn, MAX_UNIT_FILES as kt, gitHubPriorReviewsLayer as l, defaultReviewPolicy as ln, FileReviewer as lt, RetirableReview as m, resolveGuidance as mn, MAX_FILE_REVIEW_TOOL_CALLS as mt, GitHubApiFailure as n, ReviewFinding as nn, FileReviewDelegationFailure as nt, PublishedReview as o, ReviewVerdict as on, FileReviewToolkitLayer as ot, parseGitHubSubmittedAt as p, readFileHandler as pn, MAX_CHILD_FINDINGS as pt, computeChangesetFingerprint as q, MAX_CONCERNS as qt, GitHubReviewTarget as r, ReviewMission as rn, FileReviewReport as rt, ReviewPublisher as s, WalkthroughEntry as sn, FileReviewUnitFailed as st, DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN as t, ReviewConcern as tn, FileReviewBrief as tt, gitHubPullRequestSourceLayer as u, listChangedFilesHandler as un, ListReviewUnits as ut, ReviewRetirementReport as v, PullRequestMetadata as vn, fanOutHandlersLayerFor as vt, ReviewExecutionContext as w, ChangedFileStatus as wn, makeFanOutReviewSuite as wt, retireStaleReviews as x, ReviewInputViolation as xn, fileReviewPolicy as xt, decideReviewRetirement as y, PullRequestSource as yn, fanOutReviewInstructions as yt, selectReviewRange as z, FileDiffQuery as zt };
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 };
1846
2273
 
1847
- //# sourceMappingURL=github-DnenG3be.mjs.map
2274
+ //# sourceMappingURL=github-DSqZp3Ce.mjs.map