@effect-agent/pr-review 0.1.0-beta.12 → 0.1.0-beta.14

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.
Files changed (39) hide show
  1. package/README.md +53 -14
  2. package/dist/action.d.mts +13 -3
  3. package/dist/action.mjs +35 -5
  4. package/dist/action.mjs.map +1 -1
  5. package/dist/cli.mjs +2 -2
  6. package/dist/{fan-out-TrA9EUCr.d.mts → fan-out-DBHPcJwC.d.mts} +133 -35
  7. package/dist/{github-5TCFrxfX.mjs → github-mmanX6hk.mjs} +204 -40
  8. package/dist/github-mmanX6hk.mjs.map +1 -0
  9. package/dist/index.d.mts +90 -4
  10. package/dist/index.mjs +4 -3
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/logging-Q4j0oub-.mjs +75 -0
  13. package/dist/logging-Q4j0oub-.mjs.map +1 -0
  14. package/dist/{providers-DobNWMUn.mjs → providers-DD2GdrXQ.mjs} +387 -26
  15. package/dist/providers-DD2GdrXQ.mjs.map +1 -0
  16. package/dist/testing.d.mts +2 -1
  17. package/dist/testing.mjs +23 -15
  18. package/dist/testing.mjs.map +1 -1
  19. package/package.json +2 -2
  20. package/src/action.ts +60 -2
  21. package/src/index.ts +2 -0
  22. package/src/internal/action-entry.ts +2 -0
  23. package/src/internal/coverage.ts +42 -4
  24. package/src/internal/diff.ts +59 -0
  25. package/src/internal/fan-out.ts +23 -3
  26. package/src/internal/fingerprint.ts +1 -1
  27. package/src/internal/fixtures.ts +15 -4
  28. package/src/internal/github-env.ts +8 -1
  29. package/src/internal/github.ts +73 -12
  30. package/src/internal/logging.ts +124 -0
  31. package/src/internal/progress.ts +433 -0
  32. package/src/internal/render.ts +251 -19
  33. package/src/internal/retirement.ts +5 -1
  34. package/src/internal/review-agent.ts +84 -10
  35. package/src/internal/review-state.ts +21 -1
  36. package/src/internal/review-units.ts +17 -11
  37. package/src/internal/run.ts +33 -2
  38. package/dist/github-5TCFrxfX.mjs.map +0 -1
  39. package/dist/providers-DobNWMUn.mjs.map +0 -1
@@ -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 } from "effect-agent";
2
+ import { Agent, AgentPolicy, Subagent, SubagentPolicy, SubagentRuntime, 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
@@ -24,8 +24,57 @@ var ChangedFile = class extends Schema.Class("@effect-agent/pr-review/ChangedFil
24
24
  /** Present for renames/copies: the path the file previously had. */
25
25
  previousPath: Schema.optionalKey(ChangedPath),
26
26
  /** Unified-diff hunks; absent for binary or oversized files. */
27
- patch: Schema.optionalKey(Schema.String)
27
+ patch: Schema.optionalKey(Schema.String),
28
+ /**
29
+ * Bounded UTF-8 content used only when the provider omitted `patch`.
30
+ * Modified files require both sides; additions require head content and
31
+ * deletions require base content. These values are review evidence, never
32
+ * GitHub inline-comment anchors.
33
+ */
34
+ reviewBaseContent: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(2e5))),
35
+ reviewHeadContent: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(2e5)))
28
36
  }) {};
37
+ /** Complete rendered fallback evidence must fit one ordinary model context. */
38
+ const MAX_REVIEW_CONTENT_CHARS = 22e4;
39
+ /**
40
+ * Render complete patchless evidence, or refuse it when a required side is
41
+ * absent or B/H annotation would exceed the model-facing bound. Callers use
42
+ * this same value for planning and tool output so truncated fallback evidence
43
+ * can never count as complete coverage.
44
+ */
45
+ const renderReviewContent = (file) => {
46
+ if (file.patch !== void 0) return void 0;
47
+ const includeBase = file.status !== "added";
48
+ const includeHead = file.status !== "removed";
49
+ const sections = ["[GitHub omitted the unified diff. B/H lines below are bounded full-file review content, not valid inline-comment anchors. Report defects from this evidence as non-anchored concerns.]"];
50
+ let renderedLength = sections[0]?.length ?? 0;
51
+ const append = (part) => {
52
+ const nextLength = renderedLength + 1 + part.length;
53
+ if (nextLength > 22e4) return false;
54
+ sections.push(part);
55
+ renderedLength = nextLength;
56
+ return true;
57
+ };
58
+ const appendSide = (side, header, content) => {
59
+ if (!append(header)) return false;
60
+ const lines = content.split("\n");
61
+ for (let index = 0; index < lines.length; index += 1) if (!append(`${side}${index + 1} ${lines[index] ?? ""}`)) return false;
62
+ return true;
63
+ };
64
+ if (includeBase) {
65
+ if (file.reviewBaseContent === void 0) return void 0;
66
+ if (!appendSide("B", "[BASE VERSION]", file.reviewBaseContent)) return void 0;
67
+ }
68
+ if (includeHead) {
69
+ if (file.reviewHeadContent === void 0) return void 0;
70
+ if (!appendSide("H", "[HEAD VERSION]", file.reviewHeadContent)) return void 0;
71
+ }
72
+ return sections.join("\n");
73
+ };
74
+ /** Whether complete patchless evidence fits the model-facing review bound. */
75
+ const hasReviewableContent = (file) => renderReviewContent(file) !== void 0;
76
+ /** Whether the reviewer has either a real patch or bounded textual fallback evidence. */
77
+ const isReviewableFile = (file) => file.patch !== void 0 || hasReviewableContent(file);
29
78
  const HUNK_HEADER = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
30
79
  /**
31
80
  * Parse unified-diff hunk text into coordinate-tagged lines. Lines outside a
@@ -188,6 +237,8 @@ const MAX_FINDINGS = 20;
188
237
  const MAX_CONCERNS = 10;
189
238
  /** Annotated patches larger than this are truncated with an explicit marker. */
190
239
  const MAX_PATCH_CHARS = 6e4;
240
+ /** The encoded Tool result must retain one complete bounded content fallback. */
241
+ const REVIEW_TOOL_RESULT_MAX_BYTES = 2 * 1024 * 1024;
191
242
  /** One `read_file` slice never exceeds this many lines. */
192
243
  const MAX_SLICE_LINES = 1e3;
193
244
  const DEFAULT_SLICE_LINES = 400;
@@ -196,7 +247,9 @@ var ChangedFileSummary = class extends Schema.Class("@effect-agent/pr-review/Cha
196
247
  status: ChangedFileStatus,
197
248
  additions: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
198
249
  deletions: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
199
- hasTextualDiff: Schema.Boolean
250
+ hasTextualDiff: Schema.Boolean,
251
+ /** True when a missing patch was recovered as bounded UTF-8 base/head content. */
252
+ hasReviewableContent: Schema.Boolean
200
253
  }) {};
201
254
  var ChangedFilesView = class extends Schema.Class("@effect-agent/pr-review/ChangedFilesView")({
202
255
  totalFiles: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
@@ -219,16 +272,23 @@ var FileDiffQuery = class extends Schema.Class("@effect-agent/pr-review/FileDiff
219
272
  var FileDiffView = class extends Schema.Class("@effect-agent/pr-review/FileDiffView")({
220
273
  path: ChangedPath,
221
274
  status: ChangedFileStatus,
275
+ reviewMode: Schema.Literals([
276
+ "diff",
277
+ "content",
278
+ "unavailable"
279
+ ]),
222
280
  /**
223
281
  * The unified diff with explicit RIGHT-side line numbers: `R<n>` marks a
224
282
  * line present in the new file version (only those may anchor findings);
225
- * `-` marks removed lines. Empty when no textual diff exists.
283
+ * `-` marks removed lines. For content fallback, `B<n>` and `H<n>`
284
+ * identify base/head lines for reading only; they are never valid anchors.
285
+ * Empty only when neither a patch nor bounded textual content exists.
226
286
  */
227
287
  annotatedPatch: Schema.String,
228
288
  truncated: Schema.Boolean
229
289
  }) {};
230
290
  const ReadFileDiff = Tool.make("read_file_diff", {
231
- description: "Read the annotated unified diff of one changed file. Lines marked R<number> exist in the new version and are the only valid finding anchors.",
291
+ 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.",
232
292
  parameters: FileDiffQuery,
233
293
  success: FileDiffView,
234
294
  failure: Schema.Union([PullRequestSourceFailure, ReviewInputViolation]),
@@ -275,7 +335,8 @@ const listChangedFilesHandler = (_query) => Effect.gen(function* () {
275
335
  status: file.status,
276
336
  additions: file.additions,
277
337
  deletions: file.deletions,
278
- hasTextualDiff: file.patch !== void 0
338
+ hasTextualDiff: file.patch !== void 0,
339
+ hasReviewableContent: hasReviewableContent(file)
279
340
  }))
280
341
  });
281
342
  });
@@ -291,11 +352,14 @@ const readFileDiffHandler = (query) => Effect.gen(function* () {
291
352
  input: relative,
292
353
  reason: "Path is not part of this pull request's changeset."
293
354
  });
294
- const annotated = file.patch === void 0 ? "" : annotatePatch(file.patch);
295
- const truncated = annotated.length > MAX_PATCH_CHARS;
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;
296
359
  return FileDiffView.make({
297
360
  path: file.path,
298
361
  status: file.status,
362
+ reviewMode,
299
363
  annotatedPatch: truncated ? `${annotated.slice(0, MAX_PATCH_CHARS)}\n[diff truncated]` : annotated,
300
364
  truncated
301
365
  });
@@ -343,12 +407,30 @@ const FindingSeverity = Schema.Literals([
343
407
  "important",
344
408
  "nit"
345
409
  ]);
410
+ /**
411
+ * What kind of problem a finding names. Model-claimed like severity — it is a
412
+ * label for scanning a busy review, never an input to the check conclusion.
413
+ */
414
+ const FindingCategory = Schema.Literals([
415
+ "correctness",
416
+ "security",
417
+ "concurrency",
418
+ "performance",
419
+ "resources",
420
+ "error-handling",
421
+ "testing",
422
+ "maintainability",
423
+ "style",
424
+ "docs"
425
+ ]);
346
426
  var ReviewFinding = class extends Schema.Class("@effect-agent/pr-review/ReviewFinding")({
347
427
  path: ChangedPath,
348
428
  /** 1-based line numbers in the NEW file version; must appear in the diff. */
349
429
  startLine: Schema.Int.check(Schema.isGreaterThan(0)),
350
430
  endLine: Schema.Int.check(Schema.isGreaterThan(0)),
351
431
  severity: FindingSeverity,
432
+ /** Optional problem-kind label rendered next to the severity. */
433
+ category: Schema.optionalKey(FindingCategory),
352
434
  title: Schema.NonEmptyString.check(Schema.isMaxLength(120)),
353
435
  body: Schema.NonEmptyString.check(Schema.isMaxLength(2e3)),
354
436
  /** Replacement for exactly lines startLine..endLine; omit when unsure. */
@@ -371,12 +453,26 @@ var ReviewConcern = class extends Schema.Class("@effect-agent/pr-review/ReviewCo
371
453
  title: Schema.NonEmptyString.check(Schema.isMaxLength(120)),
372
454
  body: Schema.NonEmptyString.check(Schema.isMaxLength(2e3))
373
455
  }) {};
456
+ /** The per-entry walkthrough summary bound, and the entries bound (the changeset cap). */
457
+ const MAX_WALKTHROUGH_SUMMARY_CHARS = 240;
458
+ const MAX_WALKTHROUGH_ENTRIES = 300;
459
+ /**
460
+ * One reviewed file's one-sentence change summary. Rendered only when the
461
+ * path is actually part of the changeset — like finding anchors, walkthrough
462
+ * paths are validated host-side and invented ones are dropped.
463
+ */
464
+ var WalkthroughEntry = class extends Schema.Class("@effect-agent/pr-review/WalkthroughEntry")({
465
+ path: ChangedPath,
466
+ summary: Schema.NonEmptyString.check(Schema.isMaxLength(240))
467
+ }) {};
374
468
  var CodeReview = class extends Schema.Class("@effect-agent/pr-review/CodeReview")({
375
469
  summary: Schema.NonEmptyString.check(Schema.isMaxLength(4e3)),
376
470
  verdict: ReviewVerdict,
377
471
  findings: Schema.Array(ReviewFinding).check(Schema.isMaxLength(20)),
378
472
  /** Non-anchorable concerns; absent when the review raises none. */
379
- concerns: Schema.optionalKey(Schema.Array(ReviewConcern).check(Schema.isMaxLength(10)))
473
+ concerns: Schema.optionalKey(Schema.Array(ReviewConcern).check(Schema.isMaxLength(10))),
474
+ /** Per-file change summaries; absent when the model provides none. */
475
+ walkthrough: Schema.optionalKey(Schema.Array(WalkthroughEntry).check(Schema.isMaxLength(300)))
380
476
  }) {};
381
477
  const resolveGuidance = (guidance, mission) => {
382
478
  if (guidance === void 0) return [];
@@ -393,15 +489,16 @@ const makeReviewInstructions = (options = {}) => (mission) => {
393
489
  mission.body.length > 0 ? `Author description:\n${mission.body}` : "The author provided no description.",
394
490
  ...resolveGuidance(options.guidance, mission),
395
491
  "Work in this order:",
396
- "1. Call list_changed_files once to see the changeset.",
397
- "2. Call read_file_diff for every file you review. In its output, only lines marked R<number> exist in the new version; those numbers are the only valid values for startLine and endLine. Never anchor a finding to a removed (-) line.",
398
- "3. 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 honestly in your summary when it matters.",
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.",
494
+ "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.",
399
495
  "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.",
400
496
  "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.",
401
497
  "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.",
402
498
  "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.",
403
499
  "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.",
404
- "6. 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\">, \"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>}.",
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.`,
501
+ "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>}.",
405
502
  `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.`,
406
503
  "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."
407
504
  ].join("\n");
@@ -414,8 +511,10 @@ const defaultReviewPolicy = AgentPolicy.make({
414
511
  maxToolCalls: 24,
415
512
  maxDuration: "8 minutes",
416
513
  toolConcurrency: 2,
514
+ repeatedFailureLimit: 12,
417
515
  tokenBudget: 3e5,
418
516
  contextTokenLimit: 15e4,
517
+ toolResultBounds: ToolResultBounds.make({ maxBytes: REVIEW_TOOL_RESULT_MAX_BYTES }),
419
518
  onExhaustion: "final-answer"
420
519
  });
421
520
  const PullRequestReviewer = Agent.define("pr-reviewer", {
@@ -456,7 +555,7 @@ var ReviewUnitPlan = class extends Schema.Class("@effect-agent/pr-review/ReviewU
456
555
  /** True when the source returned fewer files than the pull request has. */
457
556
  truncated: Schema.Boolean,
458
557
  units: Schema.Array(ReviewUnit).check(Schema.isMaxLength(8)),
459
- /** Changed files without a textual diff; no finding can anchor to them. */
558
+ /** Changed files with neither a textual diff nor bounded base/head text. */
460
559
  undiffablePaths: Schema.Array(ChangedPath).check(Schema.isMaxLength(300)),
461
560
  /**
462
561
  * Diffable files beyond the fan-out capacity (MAX_REVIEW_UNITS units of
@@ -465,7 +564,11 @@ var ReviewUnitPlan = class extends Schema.Class("@effect-agent/pr-review/ReviewU
465
564
  */
466
565
  unassignedPaths: Schema.Array(ChangedPath).check(Schema.isMaxLength(300))
467
566
  }) {};
468
- const fileCost = (file) => file.additions + file.deletions + FILE_OVERHEAD_LINES;
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;
571
+ };
469
572
  const unitOf = (index, files) => ReviewUnit.make({
470
573
  unitId: `unit-${String(index + 1).padStart(3, "0")}`,
471
574
  paths: files.map((file) => file.path),
@@ -479,22 +582,24 @@ const unitOf = (index, files) => ReviewUnit.make({
479
582
  * then packed greedily in that order under the soft changed-line budget and
480
583
  * the hard per-unit file bound. Capacity is finite and explicit:
481
584
  *
482
- * - files without a textual diff are not delegated no finding can anchor
483
- * to them (anchor validation demands a parsed patch), so they surface in
484
- * `undiffablePaths` instead of consuming a child's budget;
485
- * - diffable files beyond `MAX_REVIEW_UNITS` full units surface in
585
+ * - files without a textual diff are still delegated when the source
586
+ * recovered complete bounded UTF-8 base/head content. Findings from that
587
+ * evidence cannot anchor inline and are reported as concerns;
588
+ * - files with neither form of textual evidence surface in
589
+ * `undiffablePaths` instead of laundering missing coverage;
590
+ * - reviewable files beyond `MAX_REVIEW_UNITS` full units surface in
486
591
  * `unassignedPaths` so the review can report them as unreviewed, never
487
592
  * silently truncated.
488
593
  */
489
594
  const planReviewUnits = (files, options) => {
490
595
  const ordered = [...files].sort((left, right) => left.path < right.path ? -1 : 1);
491
- const diffable = ordered.filter((file) => file.patch !== void 0);
492
- const undiffable = ordered.filter((file) => file.patch === void 0);
596
+ const reviewable = ordered.filter(isReviewableFile);
597
+ const undiffable = ordered.filter((file) => !isReviewableFile(file));
493
598
  const groups = [];
494
599
  const unassigned = [];
495
600
  let current = [];
496
601
  let currentCost = 0;
497
- for (const file of diffable) {
602
+ for (const file of reviewable) {
498
603
  const cost = fileCost(file);
499
604
  if (current.length >= 12 || current.length > 0 && currentCost + cost > 800) {
500
605
  groups.push(current);
@@ -573,7 +678,9 @@ var FileReviewReport = class extends Schema.Class("@effect-agent/pr-review/FileR
573
678
  unitId: ReviewUnitId,
574
679
  findings: Schema.Array(ReviewFinding).check(Schema.isMaxLength(8)),
575
680
  /** Unit-scoped concerns with no diff line to anchor to. */
576
- concerns: Schema.optionalKey(Schema.Array(ReviewConcern).check(Schema.isMaxLength(3)))
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)))
577
684
  }) {};
578
685
  const staticGuidanceLines = (guidance) => {
579
686
  if (guidance === void 0) return [];
@@ -584,12 +691,13 @@ const makeFileReviewerInstructions = (options = {}) => (brief) => [
584
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}.`,
585
692
  ...staticGuidanceLines(options.guidance),
586
693
  "Work in this order:",
587
- "1. Call read_file_diff for every file in your unit. In its output, only lines marked R<number> exist in the new version; those numbers are the only valid values for startLine and endLine. Never anchor a finding to a removed (-) line.",
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.",
588
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.",
589
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.",
590
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.",
591
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.",
592
- `4. 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">, "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>}.`,
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>}.`,
593
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.`
594
702
  ].join("\n");
595
703
  const fileReviewerInstructions = makeFileReviewerInstructions();
@@ -599,8 +707,10 @@ const defaultFileReviewerPolicy = AgentPolicy.make({
599
707
  maxToolCalls: MAX_FILE_REVIEW_TOOL_CALLS,
600
708
  maxDuration: "6 minutes",
601
709
  toolConcurrency: 2,
710
+ repeatedFailureLimit: 12,
602
711
  tokenBudget: 2e5,
603
712
  contextTokenLimit: 15e4,
713
+ toolResultBounds: ToolResultBounds.make({ maxBytes: REVIEW_TOOL_RESULT_MAX_BYTES }),
604
714
  onExhaustion: "fail"
605
715
  });
606
716
  /** The model-decoded delegation parameters: which unit to review. */
@@ -613,7 +723,9 @@ var FileReviewUnitResult = class extends Schema.Class("@effect-agent/pr-review/F
613
723
  unitId: ReviewUnitId,
614
724
  findings: Schema.Array(ReviewFinding).check(Schema.isMaxLength(8)),
615
725
  /** Unit-scoped concerns with no diff line to anchor to. */
616
- concerns: Schema.optionalKey(Schema.Array(ReviewConcern).check(Schema.isMaxLength(3)))
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)))
617
729
  }) {};
618
730
  /**
619
731
  * One unit's review failed: the child Run ended in a typed failure (policy
@@ -680,7 +792,8 @@ const makeFanOutReviewInstructions = (options = {}) => (mission) => {
680
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.",
681
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.`,
682
794
  `5. Merge the units' concerns the same way: drop duplicates keeping the most severe, and keep at most 10.`,
683
- "6. 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\">, \"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>}. Copy findings and concerns verbatim from the delegation results; never invent or edit anchors.",
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.",
684
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."
685
798
  ].join("\n");
686
799
  };
@@ -723,7 +836,8 @@ const makeFileReviewDelegation = (child) => Subagent.define("delegate_file_revie
723
836
  projectResult: (report) => Effect.succeed(FileReviewUnitResult.make({
724
837
  unitId: report.unitId,
725
838
  findings: report.findings,
726
- ...report.concerns !== void 0 ? { concerns: report.concerns } : {}
839
+ ...report.concerns !== void 0 ? { concerns: report.concerns } : {},
840
+ ...report.fileSummaries !== void 0 ? { fileSummaries: report.fileSummaries } : {}
727
841
  })),
728
842
  policy: fileReviewPolicy
729
843
  });
@@ -805,7 +919,7 @@ const SECTION = "";
805
919
  * Canonical changeset encoding: sorted by path so provider ordering never
806
920
  * matters, with every review-relevant field of every file.
807
921
  */
808
- const canonicalChangeset = (files) => files.map((file) => `${file.path}${FIELD}${file.status}${FIELD}${String(file.additions)}${FIELD}${String(file.deletions)}${FIELD}${file.patch ?? ""}`).sort().join(RECORD);
922
+ const canonicalChangeset = (files) => files.map((file) => `${file.path}${FIELD}${file.status}${FIELD}${String(file.additions)}${FIELD}${String(file.deletions)}${FIELD}${file.patch ?? ""}${FIELD}${file.reviewBaseContent ?? ""}${FIELD}${file.reviewHeadContent ?? ""}`).sort().join(RECORD);
809
923
  /**
810
924
  * Fingerprint one review's complete input surface: the (already
811
925
  * ignore-filtered) changeset plus the caller's prompt signature — the
@@ -1068,9 +1182,21 @@ var ReviewExecutionContext = class extends Context.Service()("@effect-agent/pr-r
1068
1182
  const selectedPullRequestSourceLayer = (selection) => Layer.effect(PullRequestSource)(Effect.gen(function* () {
1069
1183
  const source = yield* PullRequestSource;
1070
1184
  const selectedPaths = new Set(selection.files.map((file) => file.path));
1185
+ const selectedFiles = source.changedFiles.pipe(Effect.map((fullFiles) => {
1186
+ const fullByPath = new Map(fullFiles.map((file) => [file.path, file]));
1187
+ return selection.files.map((file) => {
1188
+ if (file.patch !== void 0) return file;
1189
+ const full = fullByPath.get(file.path);
1190
+ return full === void 0 ? file : ChangedFile.make({
1191
+ ...file,
1192
+ ...full.reviewBaseContent === void 0 ? {} : { reviewBaseContent: full.reviewBaseContent },
1193
+ ...full.reviewHeadContent === void 0 ? {} : { reviewHeadContent: full.reviewHeadContent }
1194
+ });
1195
+ });
1196
+ }));
1071
1197
  return PullRequestSource.of({
1072
1198
  metadata: source.metadata,
1073
- changedFiles: Effect.succeed(selection.files),
1199
+ changedFiles: selectedFiles,
1074
1200
  anchorFiles: source.anchorFiles,
1075
1201
  readFile: (path) => selectedPaths.has(path) ? source.readFile(path) : Effect.fail(ReviewInputViolation.make({
1076
1202
  input: path,
@@ -1140,7 +1266,7 @@ const STATE_PATTERN = /<!-- effect-agent-pr-review state-v1:[A-Za-z0-9+/]+={0,2}
1140
1266
  const RETIRED_ORIGINAL_PATTERN = /<!-- effect-agent-pr-review retired-original:start -->\n([\s\S]*?)\n<!-- effect-agent-pr-review retired-original:end -->/;
1141
1267
  const MACHINE_COMMENT_PATTERN = new RegExp(`${REVIEW_METADATA_PATTERN.source}|${FINGERPRINT_PATTERN.source}|${STATE_PATTERN.source}`, "g");
1142
1268
  const VERDICT_CALLOUT_PATTERN = /^(?:> \[!(?:CAUTION|IMPORTANT)\]\n> [^\n]*(?:\n> [^\n]*)*|> (?:ℹ️|✅)[^\n]*)\n*/;
1143
- const INLINE_FINDING_TITLE_PATTERN = /^\*\*\[(?:🛑 blocking|⚠️ important|💅 nit)\] ([^\n]+)\*\*$/;
1269
+ const INLINE_FINDING_TITLE_PATTERN = /^\*\*\[(?:🛑 blocking|⚠️ important|💅 nit)(?: · [a-z-]+)?\] ([^\n]+)\*\*$/;
1144
1270
  const MAX_REVIEW_BODY_CHARS = 6e4;
1145
1271
  /** The host-authored metadata marker is the authority gate for any edit. */
1146
1272
  const hasReviewMetadataMarker = (body) => /<!-- effect-agent-pr-review metadata\n/.test(body);
@@ -1437,22 +1563,60 @@ const gitHubPullRequestSourceLayer = Layer.effect(PullRequestSource)(Effect.gen(
1437
1563
  return all;
1438
1564
  });
1439
1565
  const metadata = yield* Effect.cached(fetchMetadata.pipe(Effect.provideService(HttpClient.HttpClient, client)));
1440
- const changedFiles = yield* Effect.cached(fetchFiles.pipe(Effect.provideService(HttpClient.HttpClient, client)));
1441
- const readFile = (path) => Effect.gen(function* () {
1566
+ const rawFiles = yield* Effect.cached(fetchFiles.pipe(Effect.provideService(HttpClient.HttpClient, client)));
1567
+ const readRepositoryFile = (path, ref) => Effect.gen(function* () {
1442
1568
  const relative = yield* normalizeRepoRelativePath(path);
1443
- if (!(yield* changedFiles).some((file) => file.path === relative)) return yield* ReviewInputViolation.make({
1569
+ const encodedPath = relative.split("/").map(encodeURIComponent).join("/");
1570
+ const buffer = yield* (yield* executeOk("readFile", withCommonHeaders(HttpClientRequest.get(`${target.apiUrl}/repos/${target.repository}/contents/${encodedPath}`).pipe(HttpClientRequest.accept("application/vnd.github.raw+json"), HttpClientRequest.setUrlParams({ ref })), target.token)).pipe(Effect.provideService(HttpClient.HttpClient, client))).arrayBuffer.pipe(Effect.mapError(failWith("readFile")));
1571
+ if (buffer.byteLength > 2e5) return yield* ReviewInputViolation.make({
1444
1572
  input: relative,
1445
- reason: "Path is not part of this pull request's changeset."
1573
+ reason: `File is larger than the ${MAX_FILE_CHARS}-byte read bound.`
1574
+ });
1575
+ const text = yield* Effect.try({
1576
+ try: () => new TextDecoder("utf-8", { fatal: true }).decode(buffer),
1577
+ catch: () => ReviewInputViolation.make({
1578
+ input: relative,
1579
+ reason: "File is not valid UTF-8 text."
1580
+ })
1581
+ });
1582
+ if (text.includes("\0")) return yield* ReviewInputViolation.make({
1583
+ input: relative,
1584
+ reason: "File contains binary NUL bytes."
1446
1585
  });
1447
- const head = yield* metadata;
1448
- const encodedPath = relative.split("/").map(encodeURIComponent).join("/");
1449
- const text = yield* (yield* executeOk("readFile", withCommonHeaders(HttpClientRequest.get(`${target.apiUrl}/repos/${target.repository}/contents/${encodedPath}`).pipe(HttpClientRequest.accept("application/vnd.github.raw+json"), HttpClientRequest.setUrlParams({ ref: head.headSha })), target.token)).pipe(Effect.provideService(HttpClient.HttpClient, client))).text.pipe(Effect.mapError(failWith("readFile")));
1450
1586
  if (text.length > 2e5) return yield* ReviewInputViolation.make({
1451
1587
  input: relative,
1452
1588
  reason: `File is larger than the ${MAX_FILE_CHARS}-character read bound.`
1453
1589
  });
1454
1590
  return text;
1455
1591
  });
1592
+ const changedFiles = yield* Effect.cached(Effect.gen(function* () {
1593
+ const [files, pullRequest] = yield* Effect.all([rawFiles, metadata]);
1594
+ return yield* Effect.forEach(files, (file) => {
1595
+ if (file.patch !== void 0) return Effect.succeed(file);
1596
+ const basePath = file.previousPath ?? file.path;
1597
+ const base = file.status === "added" ? Effect.succeed(Option.none()) : readRepositoryFile(basePath, pullRequest.baseSha ?? pullRequest.baseRef).pipe(Effect.option);
1598
+ const head = file.status === "removed" ? Effect.succeed(Option.none()) : readRepositoryFile(file.path, pullRequest.headSha).pipe(Effect.option);
1599
+ return Effect.all({
1600
+ base,
1601
+ head
1602
+ }).pipe(Effect.map(({ base, head }) => ChangedFile.make({
1603
+ ...file,
1604
+ ...Option.isSome(base) ? { reviewBaseContent: base.value } : {},
1605
+ ...Option.isSome(head) ? { reviewHeadContent: head.value } : {}
1606
+ })));
1607
+ }, { concurrency: 4 });
1608
+ }));
1609
+ const readFile = (path) => Effect.gen(function* () {
1610
+ const relative = yield* normalizeRepoRelativePath(path);
1611
+ const file = (yield* changedFiles).find((candidate) => candidate.path === relative);
1612
+ if (file === void 0) return yield* ReviewInputViolation.make({
1613
+ input: relative,
1614
+ reason: "Path is not part of this pull request's changeset."
1615
+ });
1616
+ if (file.reviewHeadContent !== void 0) return file.reviewHeadContent;
1617
+ const head = yield* metadata;
1618
+ return yield* readRepositoryFile(relative, head.headSha);
1619
+ });
1456
1620
  return PullRequestSource.of({
1457
1621
  metadata,
1458
1622
  changedFiles,
@@ -1671,6 +1835,6 @@ const fingerprintUnchanged = (current) => Effect.gen(function* () {
1671
1835
  return Option.isSome(latest) && latest.value === current;
1672
1836
  });
1673
1837
  //#endregion
1674
- export { FanOutReviewToolkit as $, ReviewMission as $t, ReviewStateAuthenticator as A, ReviewUnit as At, selectedPullRequestSourceLayer as B, FileDiffView as Bt, MAX_REVIEW_STATE_MARKER_CHARS as C, parsePatch as Cn, makeFanOutReviewInstructions as Ct, ReviewScopeMode as D, MAX_MERGED_FINDINGS as Dt, ReviewMode as E, mapFileReviewChildFailure as Et, buildProfileMission as F, rankAndDedupeFindings as Ft, webCryptoReviewStateAuthenticatorLayer as G, ListChangedFilesQuery as Gt, toStoredFinding as H, FileSliceQuery as Ht, computeProfileFingerprint as I, ChangedFileSummary as It, extractFingerprint as J, PullRequestReviewer as Jt, FINGERPRINT_MARKER_LENGTH as K, MAX_CONCERNS as Kt, fromStoredConcern as L, ChangedFilesView as Lt, ReviewStateMarkerTooLarge as M, ReviewUnitPlan as Mt, StoredReviewConcern as N, UNIT_CHANGED_LINE_BUDGET as Nt, ReviewState as O, MAX_REVIEW_UNITS as Ot, StoredReviewFinding as P, planReviewUnits as Pt, FanOutCoordinatorToolkitLayer as Q, ReviewFinding as Qt, fromStoredFinding as R, CodeReview as Rt, GitCommitSha as S, commentableLines as Sn, fileReviewerInstructions as St, ReviewHeadComparison as T, makeFileReviewerInstructions as Tt, unavailableReviewStateAuthenticatorLayer as U, FindingSeverity as Ut, toStoredConcern as V, FileSlice as Vt, validateReviewState as W, ListChangedFiles as Wt, DelegateFileReview as X, ReadFileDiff as Xt, renderFingerprintMarker as Y, ReadFile as Yt, FanOutCoordinatorToolkit as Z, ReviewConcern as Zt, ReviewRetirementHost as _, normalizeRepoRelativePath as _n, fanOutHandlersLayer as _t, PriorReviews as a, listChangedFilesHandler as an, FileReviewToolkit as at, hasReviewMetadataMarker as b, ChangedPath as bn, fileReviewDelegation as bt, fingerprintUnchanged as c, readFileHandler as cn, FileReviewUnitResult as ct, gitHubReviewPublisherLayer as d, MAX_CHANGED_FILES as dn, ListReviewUnitsQuery as dt, ReviewToolkit as en, FanOutReviewer as et, gitHubReviewRetirementHostLayer as f, MAX_FILE_CHARS as fn, MAX_CHILD_CONCERNS as ft, ReviewRetirementFailure as g, ReviewInputViolation as gn, defaultFileReviewerPolicy as gt, RetirableReviewComment as h, PullRequestSourceFailure as hn, defaultFanOutPolicy as ht, PriorReviewLookupFailure as i, defaultReviewPolicy as in, FileReviewRequest as it, ReviewStateMarker as j, ReviewUnitId as jt, ReviewStateAuthenticationFailure as k, MAX_UNIT_FILES as kt, gitHubPriorReviewsLayer as l, resolveGuidance as ln, FileReviewer as lt, RetirableReview as m, PullRequestSource as mn, MAX_FILE_REVIEW_TOOL_CALLS as mt, GitHubApiFailure as n, ReviewVerdict as nn, FileReviewDelegationFailure as nt, PublishedReview as o, makeReviewInstructions as on, FileReviewToolkitLayer as ot, parseGitHubSubmittedAt as p, PullRequestMetadata as pn, MAX_CHILD_FINDINGS as pt, computeChangesetFingerprint as q, MAX_FINDINGS as qt, GitHubReviewTarget as r, clampMaxFindings as rn, FileReviewReport as rt, ReviewPublisher as s, readFileDiffHandler as sn, FileReviewUnitFailed as st, DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN as t, ReviewToolkitLayer as tn, FileReviewBrief as tt, gitHubPullRequestSourceLayer as u, reviewInstructions as un, ListReviewUnits as ut, ReviewRetirementReport as v, ChangedFile as vn, fanOutHandlersLayerFor as vt, ReviewExecutionContext as w, makeFanOutReviewSuite as wt, retireStaleReviews as x, annotatePatch as xn, fileReviewPolicy as xt, decideReviewRetirement as y, ChangedFileStatus as yn, fanOutReviewInstructions as yt, selectReviewRange as z, FileDiffQuery as zt };
1838
+ 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 };
1675
1839
 
1676
- //# sourceMappingURL=github-5TCFrxfX.mjs.map
1840
+ //# sourceMappingURL=github-mmanX6hk.mjs.map