@effect-agent/pr-review 0.1.0-beta.24 → 0.1.0-beta.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -202
- package/dist/action.d.mts +25 -16
- package/dist/action.mjs +51 -39
- package/dist/action.mjs.map +1 -1
- package/dist/cli.mjs +3 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/{fan-out-Bi1v0VaU.d.mts → fan-out-CMEsbFLk.d.mts} +432 -206
- package/dist/{github-C6jrBLA2.mjs → github-NjgxGqwM.mjs} +2141 -1658
- package/dist/github-NjgxGqwM.mjs.map +1 -0
- package/dist/index.d.mts +20 -12
- package/dist/index.mjs +3 -3
- package/dist/{providers-2Jao2ZAX.mjs → providers-CODZQCmL.mjs} +192 -79
- package/dist/providers-CODZQCmL.mjs.map +1 -0
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/src/action.ts +123 -78
- package/src/cli.ts +6 -1
- package/src/index.ts +1 -0
- package/src/internal/adjudication.ts +415 -0
- package/src/internal/coverage.ts +41 -60
- package/src/internal/fan-out.ts +56 -4
- package/src/internal/github-env.ts +9 -0
- package/src/internal/github.ts +207 -0
- package/src/internal/progress.ts +1 -1
- package/src/internal/render.ts +186 -42
- package/src/internal/retirement.ts +16 -17
- package/src/internal/review-agent.ts +39 -4
- package/src/internal/review-state.ts +161 -42
- package/src/internal/review-units.ts +10 -9
- package/src/internal/run.ts +178 -64
- package/dist/github-C6jrBLA2.mjs.map +0 -1
- package/dist/providers-2Jao2ZAX.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $
|
|
1
|
+
import { $n as ReviewFinding, At as buildPriorReviewContext, Ct as anchorViolation, Dn as toStoredConcern, On as toStoredFinding, Pn as CodeReview, Qn as ReviewConcern, Rt as renderAdjudicationContextLine, St as splitCarriedScope, Vn as ListChangedFiles, W as makeFileReviewerDefinition, Xn as ReadFile, Zn as ReadFileDiff, _ as computeProfileFingerprint, _n as buildProfileMission, _r as PullRequestMetadata, _t as ReviewInputCoverage, ar as clampMaxFindings, bn as fromStoredConcern, br as ReviewInputViolation, bt as fanOutInputCoverage, d as gitHubReviewAdjudicationHostLayer, dn as StoredAdjudication, er as ReviewMission, f as gitHubReviewPublisherLayer, ft as rankAndDedupeConcerns, g as computeChangesetFingerprint, gn as adjudicationIdentity, gt as ReviewAssurance, jt as collectReviewAdjudications, l as gitHubPriorReviewsLayer, mn as StoredUnreviewedPass, mt as reviewConcernKey, n as GitHubApiFailure, nn as ReviewExecutionContext, nr as ReviewToolkitLayer, o as PublishedReview, on as ReviewState, or as defaultReviewPolicy, p as gitHubReviewRetirementHostLayer, pr as resolveGuidance, pt as rankAndDedupeFindings, q as runFanOutReview, r as GitHubReviewTarget, s as ReviewPublisher, t as DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN, u as gitHubPullRequestSourceLayer, ur as makeReviewInstructions, vn as concernIdentity, vr as PullRequestSource, vt as assessFlatReview, xn as fromStoredFinding, y as renderFingerprintMarker, yn as findingIdentity, zt as renderPriorFindingContextLine } from "./github-NjgxGqwM.mjs";
|
|
2
2
|
import { Config, Context, DateTime, Effect, FileSystem, Layer, Option, Ref, Schema } from "effect";
|
|
3
3
|
import { Agent, AgentPolicy, AgentRuntime, IdGenerator, UsageBudgetLimits, UsageTotals, getToolExecutionClass, makeUsageBudget, toRunBudgetHook } from "effect-agent";
|
|
4
4
|
import { Toolkit } from "effect/unstable/ai";
|
|
@@ -211,20 +211,43 @@ const renderDemoted = (finding, reason) => {
|
|
|
211
211
|
return `- ${`\`${finding.path}:${finding.startLine}${finding.endLine !== finding.startLine ? `-${finding.endLine}` : ""}\``} **[${findingLabel(finding)}] ${finding.title}** — ${finding.body} _(demoted: ${reason})_`;
|
|
212
212
|
};
|
|
213
213
|
const countNoun = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
214
|
-
|
|
214
|
+
const tallySeverities = (items) => {
|
|
215
|
+
const blocking = items.filter((item) => item.severity === "blocking").length;
|
|
216
|
+
const important = items.filter((item) => item.severity === "important").length;
|
|
217
|
+
return {
|
|
218
|
+
blocking,
|
|
219
|
+
important,
|
|
220
|
+
nit: items.length - blocking - important,
|
|
221
|
+
total: items.length
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
/** The validated finding + concern severities, kept separate by provenance. */
|
|
215
225
|
const severityCounts = (review, carriedFindings = [], carriedConcerns = []) => {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
...carriedConcerns.map((concern) => concern.severity)
|
|
221
|
-
];
|
|
226
|
+
const findings = tallySeverities(review.findings);
|
|
227
|
+
const concerns = tallySeverities(review.concerns ?? []);
|
|
228
|
+
const priorFindings = tallySeverities(carriedFindings);
|
|
229
|
+
const priorConcerns = tallySeverities(carriedConcerns);
|
|
222
230
|
return {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
231
|
+
findings,
|
|
232
|
+
concerns,
|
|
233
|
+
carriedFindings: priorFindings,
|
|
234
|
+
carriedConcerns: priorConcerns,
|
|
235
|
+
total: {
|
|
236
|
+
blocking: findings.blocking + concerns.blocking + priorFindings.blocking + priorConcerns.blocking,
|
|
237
|
+
important: findings.important + concerns.important + priorFindings.important + priorConcerns.important,
|
|
238
|
+
nit: findings.nit + concerns.nit + priorFindings.nit + priorConcerns.nit,
|
|
239
|
+
total: findings.total + concerns.total + priorFindings.total + priorConcerns.total
|
|
240
|
+
}
|
|
226
241
|
};
|
|
227
242
|
};
|
|
243
|
+
const joinItemCounts = (items) => items.length <= 1 ? items[0] ?? "none" : items.length === 2 ? `${items[0]} and ${items[1]}` : `${items.slice(0, -1).join(", ")}, and ${items.at(-1)}`;
|
|
244
|
+
const severityItemParts = (counts, severity) => [
|
|
245
|
+
...counts.findings[severity] === 0 ? [] : [countNoun(counts.findings[severity], `${severity} finding`)],
|
|
246
|
+
...counts.concerns[severity] === 0 ? [] : [countNoun(counts.concerns[severity], `${severity} concern`)],
|
|
247
|
+
...counts.carriedFindings[severity] === 0 ? [] : [countNoun(counts.carriedFindings[severity], `carried ${severity} finding`)],
|
|
248
|
+
...counts.carriedConcerns[severity] === 0 ? [] : [countNoun(counts.carriedConcerns[severity], `carried ${severity} concern`)]
|
|
249
|
+
];
|
|
250
|
+
const renderSeverityItems = (counts, severity) => joinItemCounts(severityItemParts(counts, severity));
|
|
228
251
|
/**
|
|
229
252
|
* The opening callout: the review's overall tier, derived HOST-SIDE from the
|
|
230
253
|
* validated severities (never from model prose), described by what GitHub
|
|
@@ -233,20 +256,38 @@ const severityCounts = (review, carriedFindings = [], carriedConcerns = []) => {
|
|
|
233
256
|
*/
|
|
234
257
|
const renderVerdictCallout = (review, options) => {
|
|
235
258
|
const counts = severityCounts(review, options.carriedFindings, options.carriedConcerns);
|
|
236
|
-
if (counts.blocking > 0) return `> [!CAUTION]\n> ${
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
259
|
+
if (counts.total.blocking > 0) return `> [!CAUTION]\n> ${renderSeverityItems(counts, "blocking")}. Do not merge before addressing ${counts.total.blocking === 1 ? "it" : "them"}.`;
|
|
260
|
+
if (options.inputCoverage?.status === "incomplete" || options.assurance?.status === "incomplete") {
|
|
261
|
+
const scope = splitCarriedScope(options);
|
|
262
|
+
const undiffableCount = scope.undiffablePaths.length;
|
|
263
|
+
const undiffableNote = undiffableCount > 0 ? ` ${countNoun(undiffableCount, "unreviewable file")} (binary or oversized) ${undiffableCount === 1 ? "has" : "have"} no diff a retry could settle — remove ${undiffableCount === 1 ? "it" : "them"} from the pull request or exclude ${undiffableCount === 1 ? "it" : "them"} with ignore globs.` : "";
|
|
264
|
+
if (!scope.retryableGap) return `> [!WARNING]\n>${undiffableNote} The check reports "incomplete" while ${undiffableCount === 1 ? "it remains" : "they remain"} part of the pull request.`;
|
|
265
|
+
const retryableCount = scope.retryablePaths.length;
|
|
266
|
+
return `> [!WARNING]\n> Review infrastructure did not settle. This is a reviewer-side gap, NOT a request to change code.${retryableCount > 0 ? ` ${countNoun(retryableCount, "affected path")} ${retryableCount === 1 ? "is" : "are"} carried forward and retried automatically on the next run.` : ""}${undiffableNote} The check reports "incomplete" until a run settles.`;
|
|
267
|
+
}
|
|
268
|
+
if (counts.total.important > 0) return `> [!IMPORTANT]\n> ${renderSeverityItems(counts, "important")} to address before merging.`;
|
|
269
|
+
if (counts.total.total > 0) return `> ℹ️ ${renderSeverityItems(counts, "nit")}; mergeable as-is.`;
|
|
270
|
+
return review.verdict === "approve" ? "> ✅ No issues found." : "> ℹ️ No review items. See the summary.";
|
|
242
271
|
};
|
|
243
272
|
const renderConcern = (concern) => [
|
|
244
273
|
`### ${severityEmoji[concern.severity]} ${concern.title}`,
|
|
274
|
+
...concern.evidencePaths === void 0 ? [] : ["", `_Affected paths: ${concern.evidencePaths.map((path) => `\`${path}\``).join(", ")}_`],
|
|
245
275
|
"",
|
|
246
276
|
concern.body
|
|
247
277
|
].join("\n");
|
|
248
278
|
const renderCarriedFinding = (finding) => `- \`${finding.path}:${finding.startLine}${finding.endLine === finding.startLine ? "" : `-${finding.endLine}`}\` **[${findingLabel(finding)}] ${finding.title}** — ${finding.body}`;
|
|
249
279
|
/**
|
|
280
|
+
* One adjudicated identity: its location (absent for unanchored concerns),
|
|
281
|
+
* title, maintainer disposition, actor, and the optional reason. Rendered in
|
|
282
|
+
* the collapsed adjudicated section so the audit distinguishes fixed,
|
|
283
|
+
* adjudicated, and still-open items.
|
|
284
|
+
*/
|
|
285
|
+
const renderAdjudicated = (adjudication) => {
|
|
286
|
+
const location = adjudication.path !== void 0 && adjudication.startLine !== void 0 && adjudication.endLine !== void 0 ? `\`${adjudication.path}:${adjudication.startLine}${adjudication.endLine === adjudication.startLine ? "" : `-${adjudication.endLine}`}\` ` : "";
|
|
287
|
+
const reason = adjudication.reason === void 0 ? "" : `: ${adjudication.reason}`;
|
|
288
|
+
return `- ${location}${adjudication.title} — ${adjudication.disposition} by @${adjudication.actor}${reason}`;
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
250
291
|
* Validate the model's walkthrough against the real changeset: entries whose
|
|
251
292
|
* path is not a changed file are dropped (the walkthrough analogue of anchor
|
|
252
293
|
* validation), duplicates keep the first entry, and the result is ordered by
|
|
@@ -299,14 +340,13 @@ const renderReviewStats = (files, totalChangedFiles, counts) => {
|
|
|
299
340
|
const additions = files.reduce((total, file) => total + file.additions, 0);
|
|
300
341
|
const deletions = files.reduce((total, file) => total + file.deletions, 0);
|
|
301
342
|
const fileCount = files.length < totalChangedFiles ? `${files.length} of ${totalChangedFiles} files` : countNoun(files.length, "file");
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
].join(", ");
|
|
343
|
+
const tally = counts.total.total === 0 ? "none" : joinItemCounts([
|
|
344
|
+
"blocking",
|
|
345
|
+
"important",
|
|
346
|
+
"nit"
|
|
347
|
+
].flatMap((severity) => severityItemParts(counts, severity)));
|
|
308
348
|
const effort = estimateReviewEffort(files);
|
|
309
|
-
return `**Changeset:** ${fileCount} (+${additions} / −${deletions}) · **
|
|
349
|
+
return `**Changeset:** ${fileCount} (+${additions} / −${deletions}) · **Review items:** ${tally} · **Review effort:** ${effort.score}/5 (${effort.label})`;
|
|
310
350
|
};
|
|
311
351
|
/** HTML comments must not contain `--`; interpolated values are sanitized. */
|
|
312
352
|
const commentSafe = (value) => value.replaceAll("--", "- -");
|
|
@@ -368,7 +408,7 @@ const planPublication = (review, files, options) => {
|
|
|
368
408
|
writtenAtSha: options.baselineSha
|
|
369
409
|
}))];
|
|
370
410
|
const consolidatedPromptWanted = promptEntries.length >= 2 || demoted.length > 0;
|
|
371
|
-
const renderHead = (concernsKept, demotedKept, omitted, walkthroughKept, promptsKept) => {
|
|
411
|
+
const renderHead = (concernsKept, demotedKept, omitted, walkthroughKept, promptsKept, adjudicationsKept) => {
|
|
372
412
|
const carriedFindings = options.carriedFindings ?? [];
|
|
373
413
|
const carriedConcerns = options.carriedConcerns ?? [];
|
|
374
414
|
const parts = [renderVerdictCallout(review, {
|
|
@@ -389,10 +429,19 @@ const planPublication = (review, files, options) => {
|
|
|
389
429
|
if (options.assurance?.status === "incomplete") parts.push("", "### ⚠️ Unsettled review passes", "", "The passes below failed on the reviewer's side after a bounded retry. Their paths are carried forward and re-reviewed automatically on the next run — do not change code to satisfy this section.", "", ...options.assurance.reasons.map((reason) => `- ${reason}`));
|
|
390
430
|
if (carriedFindings.length > 0) parts.push("", "<details>", `<summary>Unresolved findings carried from unchanged scope (${carriedFindings.length})</summary>`, "", ...carriedFindings.map(renderCarriedFinding), "", "</details>");
|
|
391
431
|
if (carriedConcerns.length > 0) {
|
|
392
|
-
parts.push("",
|
|
432
|
+
parts.push("", `### Unresolved concerns from unchanged paths (${carriedConcerns.length})`, "", "These concerns were reported in an earlier review and were not reverified in this incremental pass. They remain active because none of their affected paths changed.");
|
|
393
433
|
for (const concern of carriedConcerns) parts.push("", renderConcern(concern));
|
|
394
434
|
}
|
|
395
435
|
for (const concern of sortedConcerns.slice(0, concernsKept)) parts.push("", renderConcern(concern));
|
|
436
|
+
const adjudications = options.adjudications ?? [];
|
|
437
|
+
if (adjudications.length > 0) parts.push("", adjudicationsKept ? [
|
|
438
|
+
"<details>",
|
|
439
|
+
`<summary>Adjudicated (${adjudications.length})</summary>`,
|
|
440
|
+
"",
|
|
441
|
+
...adjudications.map(renderAdjudicated),
|
|
442
|
+
"",
|
|
443
|
+
"</details>"
|
|
444
|
+
].join("\n") : "⚠️ Adjudicated section omitted — the body exceeded GitHub's review size cap.");
|
|
396
445
|
if (files.length < options.totalChangedFiles) parts.push("", `⚠️ Input exposed ${files.length} of ${options.totalChangedFiles} changed files — the changeset exceeded the reviewer's file bound.`);
|
|
397
446
|
if (demotedKept > 0) parts.push("", "<details>", `<summary>Findings without a valid diff anchor (${demotedKept})</summary>`, "", ...sortedDemoted.slice(0, demotedKept).map(({ finding, reason }) => renderDemoted(finding, reason)), "", "</details>");
|
|
398
447
|
if (consolidatedPromptWanted) parts.push("", promptsKept ? renderConsolidatedAgentPrompt(promptEntries) : "⚠️ Consolidated agent prompt omitted — the body exceeded GitHub's review size cap.");
|
|
@@ -402,7 +451,7 @@ const planPublication = (review, files, options) => {
|
|
|
402
451
|
};
|
|
403
452
|
const counts = severityCounts(review, options.carriedFindings ?? [], options.carriedConcerns ?? []);
|
|
404
453
|
const unclean = options.inputCoverage?.status === "incomplete" || options.assurance?.status === "incomplete";
|
|
405
|
-
const event = !options.applyVerdict ? "COMMENT" : counts.blocking > 0 ? "REQUEST_CHANGES" : review.verdict === "approve" && counts.important === 0 && !unclean ? "APPROVE" : "COMMENT";
|
|
454
|
+
const event = !options.applyVerdict ? "COMMENT" : counts.total.blocking > 0 ? "REQUEST_CHANGES" : review.verdict === "approve" && counts.total.important === 0 && !unclean ? "APPROVE" : "COMMENT";
|
|
406
455
|
const tail = [
|
|
407
456
|
renderReviewMetadata({
|
|
408
457
|
headSha: options.headSha,
|
|
@@ -417,15 +466,18 @@ const planPublication = (review, files, options) => {
|
|
|
417
466
|
...options.stateMarker === void 0 ? [] : [options.stateMarker]
|
|
418
467
|
].join("\n");
|
|
419
468
|
const headBudget = 6e4 - tail.length - 1;
|
|
469
|
+
const adjudicationCount = options.adjudications?.length ?? 0;
|
|
420
470
|
let concernsKept = sortedConcerns.length;
|
|
421
471
|
let demotedKept = sortedDemoted.length;
|
|
422
472
|
let omitted = 0;
|
|
423
473
|
let walkthroughKept = true;
|
|
424
474
|
let promptsKept = true;
|
|
425
|
-
let
|
|
426
|
-
|
|
475
|
+
let adjudicationsKept = true;
|
|
476
|
+
let head = renderHead(concernsKept, demotedKept, omitted, walkthroughKept, promptsKept, adjudicationsKept);
|
|
477
|
+
while (head.length > headBudget && (promptsKept && consolidatedPromptWanted || walkthroughKept && walkthrough.length > 0 || adjudicationsKept && adjudicationCount > 0 || demotedKept > 0 || concernsKept > 0)) {
|
|
427
478
|
if (promptsKept && consolidatedPromptWanted) promptsKept = false;
|
|
428
479
|
else if (walkthroughKept && walkthrough.length > 0) walkthroughKept = false;
|
|
480
|
+
else if (adjudicationsKept && adjudicationCount > 0) adjudicationsKept = false;
|
|
429
481
|
else if (demotedKept > 0) {
|
|
430
482
|
demotedKept -= 1;
|
|
431
483
|
omitted += 1;
|
|
@@ -433,7 +485,7 @@ const planPublication = (review, files, options) => {
|
|
|
433
485
|
concernsKept -= 1;
|
|
434
486
|
omitted += 1;
|
|
435
487
|
}
|
|
436
|
-
head = renderHead(concernsKept, demotedKept, omitted, walkthroughKept, promptsKept);
|
|
488
|
+
head = renderHead(concernsKept, demotedKept, omitted, walkthroughKept, promptsKept, adjudicationsKept);
|
|
437
489
|
}
|
|
438
490
|
const body = `${head.slice(0, headBudget)}\n${tail}`;
|
|
439
491
|
return ReviewPublicationPlan.make({
|
|
@@ -477,8 +529,6 @@ var ReviewRunOutcome = class extends Schema.Class("@effect-agent/pr-review/Revie
|
|
|
477
529
|
activeFindings: Schema.Array(ReviewFinding).check(Schema.isMaxLength(20)),
|
|
478
530
|
/** All currently unresolved concerns, including concerns carried to final audit. */
|
|
479
531
|
activeConcerns: Schema.Array(ReviewConcern).check(Schema.isMaxLength(10)),
|
|
480
|
-
/** Host-owned structural coverage used by the Actions check conclusion. */
|
|
481
|
-
coverage: ReviewCoverage,
|
|
482
532
|
/** Exact path/evidence assignment, distinct from semantic review work. */
|
|
483
533
|
inputCoverage: ReviewInputCoverage,
|
|
484
534
|
/** Settlement of scheduled discovery, specialist, and verification work. */
|
|
@@ -493,17 +543,26 @@ var ReviewRunOutcome = class extends Schema.Class("@effect-agent/pr-review/Revie
|
|
|
493
543
|
usage: Schema.optionalKey(UsageTotals),
|
|
494
544
|
reviewMode: Schema.optionalKey(Schema.Literals(["incremental", "full"])),
|
|
495
545
|
reviewReason: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(1e3))),
|
|
496
|
-
state: Schema.optionalKey(ReviewState)
|
|
546
|
+
state: Schema.optionalKey(ReviewState),
|
|
547
|
+
/** Maintainer adjudications standing against this run's identities. */
|
|
548
|
+
adjudications: Schema.optionalKey(Schema.Array(StoredAdjudication).check(Schema.isMaxLength(20)))
|
|
497
549
|
}) {};
|
|
498
|
-
/**
|
|
499
|
-
|
|
550
|
+
/**
|
|
551
|
+
* Build the mission one review run frames from the source's snapshot. The
|
|
552
|
+
* optional continuity context (adjudicated identities, prior-round findings
|
|
553
|
+
* on re-reviewed scope) reaches only RUN missions — fingerprint missions stay
|
|
554
|
+
* plain so an adjudication never invalidates skip-unchanged authority.
|
|
555
|
+
*/
|
|
556
|
+
const buildReviewMission = (metadata, files, context) => ReviewMission.make({
|
|
500
557
|
repository: metadata.repository,
|
|
501
558
|
number: metadata.number,
|
|
502
559
|
title: metadata.title,
|
|
503
560
|
body: metadata.body,
|
|
504
561
|
baseRef: metadata.baseRef,
|
|
505
562
|
headRef: metadata.headRef,
|
|
506
|
-
changedFileCount: files.length
|
|
563
|
+
changedFileCount: files.length,
|
|
564
|
+
...context?.adjudicated !== void 0 && context.adjudicated.length > 0 ? { adjudicatedContext: context.adjudicated.slice(0, 20) } : {},
|
|
565
|
+
...context?.priorFindings !== void 0 && context.priorFindings.length > 0 ? { priorFindingContext: context.priorFindings.slice(0, 20) } : {}
|
|
507
566
|
});
|
|
508
567
|
/** Enforce the configured findings bound on an already-validated review. */
|
|
509
568
|
const enforceFindingsBound = (review, maxFindings) => review.findings.length <= maxFindings ? review : CodeReview.make({
|
|
@@ -515,6 +574,24 @@ const enforceFindingsBound = (review, maxFindings) => review.findings.length <=
|
|
|
515
574
|
});
|
|
516
575
|
const findingKey = (finding) => `${finding.path}\u0000${finding.startLine}\u0000${finding.endLine}\u0000${finding.severity}\u0000${finding.title}`;
|
|
517
576
|
/**
|
|
577
|
+
* Continuity inputs resolved BEFORE any model work: the standing maintainer
|
|
578
|
+
* adjudications (fresh host listing merged later-wins over the prior state's
|
|
579
|
+
* stored set) and the prior-round findings whose paths this run re-reviews.
|
|
580
|
+
* The latter are dropped from the carry (the new round re-decides them) but
|
|
581
|
+
* injected as prompt context so successive rounds do not silently contradict
|
|
582
|
+
* each other — context ONLY, never auto-carried into active findings.
|
|
583
|
+
*/
|
|
584
|
+
const resolveReviewContinuityContext = Effect.fn("resolveReviewContinuityContext")(function* () {
|
|
585
|
+
const executionContext = yield* ReviewExecutionContext;
|
|
586
|
+
const priorState = executionContext.mode === "incremental" ? executionContext.priorState : void 0;
|
|
587
|
+
const adjudications = yield* collectReviewAdjudications(priorState?.adjudications ?? []);
|
|
588
|
+
const affectedPaths = new Set(executionContext.affectedPaths);
|
|
589
|
+
return {
|
|
590
|
+
adjudications,
|
|
591
|
+
priorFindingsOnScope: priorState?.unresolvedFindings.filter((finding) => affectedPaths.has(finding.path)) ?? []
|
|
592
|
+
};
|
|
593
|
+
});
|
|
594
|
+
/**
|
|
518
595
|
* The shared settlement tail: carry unchanged prior scope, decide whether
|
|
519
596
|
* this run's continuity state can be signed, plan the exact publication, and
|
|
520
597
|
* (optionally) post it. Continuity requires only that the run COMPLETED with
|
|
@@ -523,31 +600,58 @@ const findingKey = (finding) => `${finding.path}\u0000${finding.startLine}\u0000
|
|
|
523
600
|
*/
|
|
524
601
|
const settleReviewRun = (core, context, options) => Effect.gen(function* () {
|
|
525
602
|
const { metadata, files, anchorFiles, fingerprint, usage } = context;
|
|
526
|
-
const executionContext =
|
|
527
|
-
const
|
|
603
|
+
const executionContext = yield* ReviewExecutionContext;
|
|
604
|
+
const adjudications = context.adjudications ?? [];
|
|
605
|
+
const adjudicatedIdentities = new Set(adjudications.map(adjudicationIdentity));
|
|
606
|
+
const isAdjudicatedFinding = (finding) => adjudicatedIdentities.has(findingIdentity(finding));
|
|
607
|
+
const isAdjudicatedConcern = (concern) => adjudicatedIdentities.has(concernIdentity(concern));
|
|
608
|
+
const filteredReview = adjudicatedIdentities.size === 0 ? core.review : CodeReview.make({
|
|
609
|
+
summary: core.review.summary,
|
|
610
|
+
verdict: core.review.verdict,
|
|
611
|
+
findings: core.review.findings.filter((finding) => !isAdjudicatedFinding(finding)),
|
|
612
|
+
...core.review.concerns === void 0 ? {} : { concerns: core.review.concerns.filter((concern) => !isAdjudicatedConcern(concern)) },
|
|
613
|
+
...core.review.walkthrough === void 0 ? {} : { walkthrough: core.review.walkthrough }
|
|
614
|
+
});
|
|
615
|
+
const reviewPaths = new Set(files.map((file) => file.path));
|
|
616
|
+
const normalizedReview = CodeReview.make({
|
|
617
|
+
...filteredReview,
|
|
618
|
+
...filteredReview.concerns === void 0 ? {} : { concerns: filteredReview.concerns.map((concern) => {
|
|
619
|
+
const evidencePaths = concern.evidencePaths;
|
|
620
|
+
if (evidencePaths === void 0 || evidencePaths.some((path) => !reviewPaths.has(path))) {
|
|
621
|
+
const { evidencePaths: _invalid, ...pathless } = concern;
|
|
622
|
+
return ReviewConcern.make(pathless);
|
|
623
|
+
}
|
|
624
|
+
return ReviewConcern.make({
|
|
625
|
+
...concern,
|
|
626
|
+
evidencePaths: [...new Set(evidencePaths)].sort()
|
|
627
|
+
});
|
|
628
|
+
}) }
|
|
629
|
+
});
|
|
630
|
+
const review = enforceFindingsBound(normalizedReview, clampMaxFindings(options.maxFindings));
|
|
528
631
|
const { inputCoverage, assurance } = core;
|
|
529
632
|
const unreviewedPaths = [...new Set(core.unreviewedPaths)].sort();
|
|
530
|
-
const reviewTotalFiles = executionContext
|
|
531
|
-
const affectedPaths = new Set(executionContext
|
|
532
|
-
const priorState = executionContext
|
|
533
|
-
const
|
|
534
|
-
const activeFindings = rankAndDedupeFindings([...
|
|
633
|
+
const reviewTotalFiles = executionContext.totalFiles;
|
|
634
|
+
const affectedPaths = new Set(executionContext.affectedPaths);
|
|
635
|
+
const priorState = executionContext.mode === "incremental" ? executionContext.priorState : void 0;
|
|
636
|
+
const eligibleCarriedCandidates = (priorState?.unresolvedFindings.filter((finding) => !affectedPaths.has(finding.path)).map(fromStoredFinding) ?? []).filter((finding) => !isAdjudicatedFinding(finding));
|
|
637
|
+
const activeFindings = rankAndDedupeFindings([...eligibleCarriedCandidates, ...review.findings.filter((finding) => !isAdjudicatedFinding(finding))]).slice(0, clampMaxFindings(options.maxFindings));
|
|
535
638
|
const activeFindingKeys = new Set(activeFindings.map(findingKey));
|
|
536
639
|
const currentFindingKeys = new Set(review.findings.map(findingKey));
|
|
537
|
-
const carriedFindings =
|
|
538
|
-
const
|
|
539
|
-
const activeConcerns = rankAndDedupeConcerns([...
|
|
540
|
-
const currentConcernKeys = new Set((review.concerns ?? []).map(
|
|
541
|
-
const activeConcernKeys = new Set(activeConcerns.map(
|
|
542
|
-
const carriedConcerns =
|
|
543
|
-
const key =
|
|
640
|
+
const carriedFindings = eligibleCarriedCandidates.filter((finding) => activeFindingKeys.has(findingKey(finding)) && !currentFindingKeys.has(findingKey(finding)));
|
|
641
|
+
const eligibleCarriedConcernCandidates = (priorState?.unresolvedConcerns.filter((concern) => concern.evidencePaths !== void 0 && concern.evidencePaths.every((path) => !affectedPaths.has(path))).map(fromStoredConcern) ?? []).filter((concern) => !isAdjudicatedConcern(concern));
|
|
642
|
+
const activeConcerns = rankAndDedupeConcerns([...eligibleCarriedConcernCandidates, ...(review.concerns ?? []).filter((concern) => !isAdjudicatedConcern(concern))]);
|
|
643
|
+
const currentConcernKeys = new Set((review.concerns ?? []).map(reviewConcernKey));
|
|
644
|
+
const activeConcernKeys = new Set(activeConcerns.map(reviewConcernKey));
|
|
645
|
+
const carriedConcerns = eligibleCarriedConcernCandidates.filter((concern) => {
|
|
646
|
+
const key = reviewConcernKey(concern);
|
|
544
647
|
return activeConcernKeys.has(key) && !currentConcernKeys.has(key);
|
|
545
648
|
});
|
|
546
649
|
const settled = inputCoverage.status === "complete" && assurance.status !== "incomplete" && unreviewedPaths.length === 0;
|
|
547
|
-
const
|
|
650
|
+
const concernsHaveEvidencePaths = activeConcerns.every((concern) => concern.evidencePaths !== void 0);
|
|
651
|
+
const skipFingerprint = settled && concernsHaveEvidencePaths ? fingerprint : void 0;
|
|
548
652
|
const carriedScopeFits = unreviewedPaths.length <= 100;
|
|
549
|
-
const stateCandidate =
|
|
550
|
-
version:
|
|
653
|
+
const stateCandidate = fingerprint !== void 0 && executionContext.profileFingerprint !== void 0 && metadata.baseSha !== void 0 && anchorFiles.length >= metadata.totalChangedFiles && carriedScopeFits && concernsHaveEvidencePaths && executionContext.stateAuthenticator?.status === "available" ? ReviewState.make({
|
|
654
|
+
version: 1,
|
|
551
655
|
repository: metadata.repository,
|
|
552
656
|
pullRequestNumber: metadata.number,
|
|
553
657
|
baseRef: metadata.baseRef,
|
|
@@ -555,19 +659,20 @@ const settleReviewRun = (core, context, options) => Effect.gen(function* () {
|
|
|
555
659
|
headRef: metadata.headRef,
|
|
556
660
|
reviewedHeadSha: metadata.headSha,
|
|
557
661
|
profileFingerprint: executionContext.profileFingerprint,
|
|
558
|
-
|
|
662
|
+
settledScopeFingerprint: fingerprint,
|
|
559
663
|
reviewedPathCount: anchorFiles.length,
|
|
560
664
|
unresolvedFindings: activeFindings.map(toStoredFinding),
|
|
561
665
|
unresolvedConcerns: activeConcerns.map(toStoredConcern),
|
|
562
666
|
unreviewedPaths,
|
|
563
667
|
unreviewedPasses: (core.unreviewedPasses ?? []).slice(0, 24),
|
|
564
668
|
settled,
|
|
565
|
-
lastReviewMode: executionContext.mode
|
|
669
|
+
lastReviewMode: executionContext.mode,
|
|
670
|
+
...adjudications.length === 0 ? {} : { adjudications }
|
|
566
671
|
}) : void 0;
|
|
567
|
-
const continuity = stateCandidate === void 0 || executionContext
|
|
672
|
+
const continuity = stateCandidate === void 0 || executionContext.stateAuthenticator === void 0 ? {
|
|
568
673
|
state: void 0,
|
|
569
674
|
marker: void 0,
|
|
570
|
-
notice:
|
|
675
|
+
notice: !carriedScopeFits ? `carried unreviewed scope (${unreviewedPaths.length} paths) exceeded the 100-path continuity bound` : !concernsHaveEvidencePaths ? "one or more review concerns lacked host-validated affected paths" : executionContext.stateAuthenticator?.status === "unavailable" ? executionContext.stateAuthenticator.unavailableReason ?? "authenticated continuity state is unavailable" : void 0
|
|
571
676
|
} : yield* executionContext.stateAuthenticator.render(stateCandidate).pipe(Effect.match({
|
|
572
677
|
onFailure: (error) => ({
|
|
573
678
|
state: void 0,
|
|
@@ -595,30 +700,29 @@ const settleReviewRun = (core, context, options) => Effect.gen(function* () {
|
|
|
595
700
|
unreviewedPaths,
|
|
596
701
|
carriedFindings,
|
|
597
702
|
carriedConcerns,
|
|
598
|
-
reviewMode: executionContext
|
|
599
|
-
reviewReason: executionContext
|
|
600
|
-
baselineSha: executionContext
|
|
703
|
+
reviewMode: executionContext.mode,
|
|
704
|
+
reviewReason: executionContext.reason,
|
|
705
|
+
baselineSha: executionContext.baselineSha,
|
|
601
706
|
reviewFilesVisible: files.length,
|
|
602
707
|
reviewTotalFiles,
|
|
603
708
|
stateMarker: continuity.marker,
|
|
604
|
-
stateNotice: continuity.notice
|
|
709
|
+
stateNotice: continuity.notice,
|
|
710
|
+
...adjudications.length === 0 ? {} : { adjudications }
|
|
605
711
|
});
|
|
606
712
|
const shared = {
|
|
607
713
|
review,
|
|
608
714
|
activeFindings,
|
|
609
715
|
activeConcerns,
|
|
610
|
-
coverage: compatibilityCoverage(inputCoverage, assurance),
|
|
611
716
|
inputCoverage,
|
|
612
717
|
assurance,
|
|
613
718
|
unreviewedPaths,
|
|
614
719
|
plan,
|
|
615
720
|
turns: core.turns,
|
|
616
721
|
...usage === void 0 ? {} : { usage },
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
621
|
-
...continuity.state === void 0 ? {} : { state: continuity.state }
|
|
722
|
+
reviewMode: executionContext.mode,
|
|
723
|
+
reviewReason: executionContext.reason,
|
|
724
|
+
...continuity.state === void 0 ? {} : { state: continuity.state },
|
|
725
|
+
...adjudications.length === 0 ? {} : { adjudications }
|
|
622
726
|
};
|
|
623
727
|
if (!options.post) return ReviewRunOutcome.make(shared);
|
|
624
728
|
const published = yield* (yield* ReviewPublisher).publish(plan);
|
|
@@ -638,8 +742,12 @@ const executeReview = (binding, options) => Effect.gen(function* () {
|
|
|
638
742
|
const metadata = yield* source.metadata;
|
|
639
743
|
const files = yield* source.changedFiles;
|
|
640
744
|
const anchorFiles = yield* source.anchorFiles;
|
|
641
|
-
const executionContext =
|
|
642
|
-
const
|
|
745
|
+
const executionContext = yield* ReviewExecutionContext;
|
|
746
|
+
const continuity = yield* resolveReviewContinuityContext();
|
|
747
|
+
const mission = buildReviewMission(metadata, files, {
|
|
748
|
+
adjudicated: continuity.adjudications.map(renderAdjudicationContextLine),
|
|
749
|
+
priorFindings: continuity.priorFindingsOnScope.map(renderPriorFindingContextLine)
|
|
750
|
+
});
|
|
643
751
|
const fullMission = buildReviewMission(metadata, anchorFiles);
|
|
644
752
|
const fingerprint = options.signature === void 0 ? void 0 : yield* computeChangesetFingerprint(anchorFiles, options.signature(fullMission));
|
|
645
753
|
const budget = yield* makeUsageBudget(options.limits ?? reviewBudgetLimits);
|
|
@@ -652,7 +760,7 @@ const executeReview = (binding, options) => Effect.gen(function* () {
|
|
|
652
760
|
const review = yield* Schema.decodeUnknownEffect(CodeReview)(result.output);
|
|
653
761
|
const assessment = assessFlatReview({
|
|
654
762
|
files,
|
|
655
|
-
totalFiles: executionContext
|
|
763
|
+
totalFiles: executionContext.totalFiles,
|
|
656
764
|
anchorFiles,
|
|
657
765
|
totalAnchorFiles: metadata.totalChangedFiles,
|
|
658
766
|
events
|
|
@@ -669,7 +777,8 @@ const executeReview = (binding, options) => Effect.gen(function* () {
|
|
|
669
777
|
files,
|
|
670
778
|
anchorFiles,
|
|
671
779
|
fingerprint,
|
|
672
|
-
usage
|
|
780
|
+
usage,
|
|
781
|
+
adjudications: continuity.adjudications
|
|
673
782
|
}, options);
|
|
674
783
|
});
|
|
675
784
|
/**
|
|
@@ -683,21 +792,23 @@ const executeFanOutReview = (binding, options) => Effect.gen(function* () {
|
|
|
683
792
|
const metadata = yield* source.metadata;
|
|
684
793
|
const files = yield* source.changedFiles;
|
|
685
794
|
const anchorFiles = yield* source.anchorFiles;
|
|
686
|
-
const executionContext =
|
|
795
|
+
const executionContext = yield* ReviewExecutionContext;
|
|
687
796
|
const fullMission = buildReviewMission(metadata, anchorFiles);
|
|
688
797
|
const fingerprint = options.signature === void 0 ? void 0 : yield* computeChangesetFingerprint(anchorFiles, options.signature(fullMission));
|
|
689
798
|
const budget = yield* makeUsageBudget(options.limits ?? fanOutReviewBudgetLimits);
|
|
690
|
-
const totalFiles = executionContext
|
|
799
|
+
const totalFiles = executionContext.totalFiles;
|
|
800
|
+
const continuity = yield* resolveReviewContinuityContext();
|
|
691
801
|
const pipeline = yield* runFanOutReview(binding, {
|
|
692
802
|
files,
|
|
693
803
|
anchorFiles,
|
|
694
804
|
totalChangedFiles: totalFiles,
|
|
695
805
|
maxFindings: options.maxFindings,
|
|
696
806
|
budget: toRunBudgetHook(budget),
|
|
697
|
-
...executionContext
|
|
807
|
+
...executionContext.retryPaths.length > 0 ? { retry: {
|
|
698
808
|
paths: executionContext.retryPaths,
|
|
699
809
|
stages: executionContext.retryStages
|
|
700
|
-
} } : {}
|
|
810
|
+
} } : {},
|
|
811
|
+
...continuity.adjudications.length > 0 || continuity.priorFindingsOnScope.length > 0 ? { priorContext: buildPriorReviewContext(continuity.adjudications, continuity.priorFindingsOnScope) } : {}
|
|
701
812
|
});
|
|
702
813
|
const inputCoverage = fanOutInputCoverage({
|
|
703
814
|
plan: pipeline.plan,
|
|
@@ -722,7 +833,8 @@ const executeFanOutReview = (binding, options) => Effect.gen(function* () {
|
|
|
722
833
|
files,
|
|
723
834
|
anchorFiles,
|
|
724
835
|
fingerprint,
|
|
725
|
-
usage
|
|
836
|
+
usage,
|
|
837
|
+
adjudications: continuity.adjudications
|
|
726
838
|
}, options);
|
|
727
839
|
});
|
|
728
840
|
//#endregion
|
|
@@ -933,7 +1045,7 @@ const settleCallout = (info) => {
|
|
|
933
1045
|
if (info.outcome === "failed") return "> ⚠️ **Code review run failed** — nothing was posted.";
|
|
934
1046
|
switch (info.conclusion) {
|
|
935
1047
|
case "success": return `> ✅ **Code review posted** — verdict \`${info.verdict}\`, ${info.inlineComments} inline comment(s), nothing blocking.`;
|
|
936
|
-
case "blocking": return `> 🛑 **Code review posted
|
|
1048
|
+
case "blocking": return `> 🛑 **Code review posted:** blocking review items; the check fails until they are addressed.`;
|
|
937
1049
|
case "incomplete": return `> ⚠️ **Code review posted** — input coverage or configured review assurance is incomplete, so the check fails.`;
|
|
938
1050
|
}
|
|
939
1051
|
};
|
|
@@ -1148,7 +1260,8 @@ const gitHubReviewLayers = (target) => Layer.unwrap(Effect.gen(function* () {
|
|
|
1148
1260
|
token,
|
|
1149
1261
|
reviewAuthorLogin
|
|
1150
1262
|
});
|
|
1151
|
-
|
|
1263
|
+
const adjudicationHostLayer = gitHubReviewAdjudicationHostLayer.pipe(Layer.provide(targetLayer));
|
|
1264
|
+
return Layer.mergeAll(gitHubPullRequestSourceLayer.pipe(Layer.provide(targetLayer)), gitHubReviewPublisherLayer.pipe(Layer.provide(targetLayer)), gitHubPriorReviewsLayer.pipe(Layer.provide(targetLayer)), gitHubReviewRetirementHostLayer.pipe(Layer.provide(targetLayer)), adjudicationHostLayer, gitHubReviewProgressLayer.pipe(Layer.provide(targetLayer)));
|
|
1152
1265
|
}));
|
|
1153
1266
|
//#endregion
|
|
1154
1267
|
//#region src/internal/providers.ts
|
|
@@ -1210,4 +1323,4 @@ const anthropicClientLayer = AnthropicClient.layerConfig({ apiKey: Config.redact
|
|
|
1210
1323
|
//#endregion
|
|
1211
1324
|
export { reviewBudgetLimits as A, ignoringPullRequestSourceLayer as B, PrReview as C, executeFanOutReview as D, enforceFindingsBound as E, estimateReviewEffort as F, resolveEffortRung as G, InvalidEffortInput as H, planPublication as I, planWalkthrough as L, ReviewCommentDraft as M, ReviewEvent as N, executeReview as O, ReviewPublicationPlan as P, renderAgentPrompt as R, renderProgressSettleBody as S, buildReviewMission as T, isEffortPosition as U, EFFORT_ALIASES as V, parseEffortPosition as W, gitHubReviewProgressLayer as _, anthropicClientLayer as a, renderProgressBeginBody as b, makeOpenAiReviewModel as c, ReviewTargetUnresolved as d, gitHubReviewLayers as f, ReviewProgressReporter as g, PROGRESS_COMMENT_MARKER_PREFIX as h, PROVIDER_EFFORT_RUNGS as i, AGENT_PROMPT_PREAMBLE as j, fanOutReviewBudgetLimits as k, openAiClientLayer as l, resolveReviewTarget as m, DEFAULT_PROVIDER as n, describeReviewModel as o, readGitHubEvent as p, PROVIDER_CREDENTIAL_ENV as r, makeAnthropicReviewModel as s, DEFAULT_MODEL as t, GitHubEventWire as u, noopReviewProgressReporterLayer as v, ReviewRunOutcome as w, renderProgressClaimMarker as x, parseProgressClaim as y, compileIgnoreGlobs as z };
|
|
1212
1325
|
|
|
1213
|
-
//# sourceMappingURL=providers-
|
|
1326
|
+
//# sourceMappingURL=providers-CODZQCmL.mjs.map
|