@effect-agent/pr-review 0.1.0-beta.23 → 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 -195
- package/dist/action.d.mts +27 -18
- package/dist/action.mjs +60 -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-BJBTAYuh.d.mts → fan-out-CMEsbFLk.d.mts} +455 -177
- package/dist/{github-BbwYzNrC.mjs → github-NjgxGqwM.mjs} +2163 -1518
- package/dist/github-NjgxGqwM.mjs.map +1 -0
- package/dist/index.d.mts +30 -20
- package/dist/index.mjs +3 -3
- package/dist/{providers-NyP-4rS6.mjs → providers-CODZQCmL.mjs} +202 -80
- package/dist/providers-CODZQCmL.mjs.map +1 -0
- package/dist/testing.d.mts +3 -1
- package/dist/testing.mjs +3 -2
- package/dist/testing.mjs.map +1 -1
- package/package.json +2 -2
- package/src/action.ts +141 -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/factory.ts +4 -4
- package/src/internal/fan-out.ts +208 -14
- package/src/internal/fingerprint.ts +16 -10
- package/src/internal/fixtures.ts +6 -0
- package/src/internal/github-env.ts +9 -0
- package/src/internal/github.ts +243 -7
- 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 +315 -105
- package/src/internal/review-units.ts +10 -9
- package/src/internal/run.ts +197 -63
- package/dist/github-BbwYzNrC.mjs.map +0 -1
- package/dist/providers-NyP-4rS6.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,18 +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,
|
|
667
|
+
unreviewedPasses: (core.unreviewedPasses ?? []).slice(0, 24),
|
|
563
668
|
settled,
|
|
564
|
-
lastReviewMode: executionContext.mode
|
|
669
|
+
lastReviewMode: executionContext.mode,
|
|
670
|
+
...adjudications.length === 0 ? {} : { adjudications }
|
|
565
671
|
}) : void 0;
|
|
566
|
-
const continuity = stateCandidate === void 0 || executionContext
|
|
672
|
+
const continuity = stateCandidate === void 0 || executionContext.stateAuthenticator === void 0 ? {
|
|
567
673
|
state: void 0,
|
|
568
674
|
marker: void 0,
|
|
569
|
-
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
|
|
570
676
|
} : yield* executionContext.stateAuthenticator.render(stateCandidate).pipe(Effect.match({
|
|
571
677
|
onFailure: (error) => ({
|
|
572
678
|
state: void 0,
|
|
@@ -594,30 +700,29 @@ const settleReviewRun = (core, context, options) => Effect.gen(function* () {
|
|
|
594
700
|
unreviewedPaths,
|
|
595
701
|
carriedFindings,
|
|
596
702
|
carriedConcerns,
|
|
597
|
-
reviewMode: executionContext
|
|
598
|
-
reviewReason: executionContext
|
|
599
|
-
baselineSha: executionContext
|
|
703
|
+
reviewMode: executionContext.mode,
|
|
704
|
+
reviewReason: executionContext.reason,
|
|
705
|
+
baselineSha: executionContext.baselineSha,
|
|
600
706
|
reviewFilesVisible: files.length,
|
|
601
707
|
reviewTotalFiles,
|
|
602
708
|
stateMarker: continuity.marker,
|
|
603
|
-
stateNotice: continuity.notice
|
|
709
|
+
stateNotice: continuity.notice,
|
|
710
|
+
...adjudications.length === 0 ? {} : { adjudications }
|
|
604
711
|
});
|
|
605
712
|
const shared = {
|
|
606
713
|
review,
|
|
607
714
|
activeFindings,
|
|
608
715
|
activeConcerns,
|
|
609
|
-
coverage: compatibilityCoverage(inputCoverage, assurance),
|
|
610
716
|
inputCoverage,
|
|
611
717
|
assurance,
|
|
612
718
|
unreviewedPaths,
|
|
613
719
|
plan,
|
|
614
720
|
turns: core.turns,
|
|
615
721
|
...usage === void 0 ? {} : { usage },
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
...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 }
|
|
621
726
|
};
|
|
622
727
|
if (!options.post) return ReviewRunOutcome.make(shared);
|
|
623
728
|
const published = yield* (yield* ReviewPublisher).publish(plan);
|
|
@@ -637,8 +742,12 @@ const executeReview = (binding, options) => Effect.gen(function* () {
|
|
|
637
742
|
const metadata = yield* source.metadata;
|
|
638
743
|
const files = yield* source.changedFiles;
|
|
639
744
|
const anchorFiles = yield* source.anchorFiles;
|
|
640
|
-
const executionContext =
|
|
641
|
-
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
|
+
});
|
|
642
751
|
const fullMission = buildReviewMission(metadata, anchorFiles);
|
|
643
752
|
const fingerprint = options.signature === void 0 ? void 0 : yield* computeChangesetFingerprint(anchorFiles, options.signature(fullMission));
|
|
644
753
|
const budget = yield* makeUsageBudget(options.limits ?? reviewBudgetLimits);
|
|
@@ -651,7 +760,7 @@ const executeReview = (binding, options) => Effect.gen(function* () {
|
|
|
651
760
|
const review = yield* Schema.decodeUnknownEffect(CodeReview)(result.output);
|
|
652
761
|
const assessment = assessFlatReview({
|
|
653
762
|
files,
|
|
654
|
-
totalFiles: executionContext
|
|
763
|
+
totalFiles: executionContext.totalFiles,
|
|
655
764
|
anchorFiles,
|
|
656
765
|
totalAnchorFiles: metadata.totalChangedFiles,
|
|
657
766
|
events
|
|
@@ -668,7 +777,8 @@ const executeReview = (binding, options) => Effect.gen(function* () {
|
|
|
668
777
|
files,
|
|
669
778
|
anchorFiles,
|
|
670
779
|
fingerprint,
|
|
671
|
-
usage
|
|
780
|
+
usage,
|
|
781
|
+
adjudications: continuity.adjudications
|
|
672
782
|
}, options);
|
|
673
783
|
});
|
|
674
784
|
/**
|
|
@@ -682,17 +792,23 @@ const executeFanOutReview = (binding, options) => Effect.gen(function* () {
|
|
|
682
792
|
const metadata = yield* source.metadata;
|
|
683
793
|
const files = yield* source.changedFiles;
|
|
684
794
|
const anchorFiles = yield* source.anchorFiles;
|
|
685
|
-
const executionContext =
|
|
795
|
+
const executionContext = yield* ReviewExecutionContext;
|
|
686
796
|
const fullMission = buildReviewMission(metadata, anchorFiles);
|
|
687
797
|
const fingerprint = options.signature === void 0 ? void 0 : yield* computeChangesetFingerprint(anchorFiles, options.signature(fullMission));
|
|
688
798
|
const budget = yield* makeUsageBudget(options.limits ?? fanOutReviewBudgetLimits);
|
|
689
|
-
const totalFiles = executionContext
|
|
799
|
+
const totalFiles = executionContext.totalFiles;
|
|
800
|
+
const continuity = yield* resolveReviewContinuityContext();
|
|
690
801
|
const pipeline = yield* runFanOutReview(binding, {
|
|
691
802
|
files,
|
|
692
803
|
anchorFiles,
|
|
693
804
|
totalChangedFiles: totalFiles,
|
|
694
805
|
maxFindings: options.maxFindings,
|
|
695
|
-
budget: toRunBudgetHook(budget)
|
|
806
|
+
budget: toRunBudgetHook(budget),
|
|
807
|
+
...executionContext.retryPaths.length > 0 ? { retry: {
|
|
808
|
+
paths: executionContext.retryPaths,
|
|
809
|
+
stages: executionContext.retryStages
|
|
810
|
+
} } : {},
|
|
811
|
+
...continuity.adjudications.length > 0 || continuity.priorFindingsOnScope.length > 0 ? { priorContext: buildPriorReviewContext(continuity.adjudications, continuity.priorFindingsOnScope) } : {}
|
|
696
812
|
});
|
|
697
813
|
const inputCoverage = fanOutInputCoverage({
|
|
698
814
|
plan: pipeline.plan,
|
|
@@ -707,13 +823,18 @@ const executeFanOutReview = (binding, options) => Effect.gen(function* () {
|
|
|
707
823
|
inputCoverage,
|
|
708
824
|
assurance: pipeline.assurance,
|
|
709
825
|
unreviewedPaths: pipeline.unreviewedPaths,
|
|
826
|
+
unreviewedPasses: pipeline.unreviewedPasses.slice(0, 24).map((pass) => StoredUnreviewedPass.make({
|
|
827
|
+
stage: pass.stage,
|
|
828
|
+
paths: pass.paths.slice(0, 12)
|
|
829
|
+
})),
|
|
710
830
|
turns: pipeline.turns
|
|
711
831
|
}, {
|
|
712
832
|
metadata,
|
|
713
833
|
files,
|
|
714
834
|
anchorFiles,
|
|
715
835
|
fingerprint,
|
|
716
|
-
usage
|
|
836
|
+
usage,
|
|
837
|
+
adjudications: continuity.adjudications
|
|
717
838
|
}, options);
|
|
718
839
|
});
|
|
719
840
|
//#endregion
|
|
@@ -754,8 +875,8 @@ const makeReviewSnapshot = (ignore) => provideIgnore(Effect.gen(function* () {
|
|
|
754
875
|
* Build the flat reviewer: one bounded read-only agent over the whole
|
|
755
876
|
* changeset. Returns the model-agnostic definition, the explicit binding, and
|
|
756
877
|
* a `run` whose error and requirement channels stay fully inferred — the
|
|
757
|
-
* pull-request source, the publisher, extra tool handlers,
|
|
758
|
-
* Layer's requirements all remain visible to the caller.
|
|
878
|
+
* pull-request source, the publisher, `Crypto.Crypto`, extra tool handlers,
|
|
879
|
+
* and the Model Layer's requirements all remain visible to the caller.
|
|
759
880
|
*/
|
|
760
881
|
const make = (options) => {
|
|
761
882
|
const extraTools = options.extraTools ?? EMPTY_TOOLS;
|
|
@@ -924,7 +1045,7 @@ const settleCallout = (info) => {
|
|
|
924
1045
|
if (info.outcome === "failed") return "> ⚠️ **Code review run failed** — nothing was posted.";
|
|
925
1046
|
switch (info.conclusion) {
|
|
926
1047
|
case "success": return `> ✅ **Code review posted** — verdict \`${info.verdict}\`, ${info.inlineComments} inline comment(s), nothing blocking.`;
|
|
927
|
-
case "blocking": return `> 🛑 **Code review posted
|
|
1048
|
+
case "blocking": return `> 🛑 **Code review posted:** blocking review items; the check fails until they are addressed.`;
|
|
928
1049
|
case "incomplete": return `> ⚠️ **Code review posted** — input coverage or configured review assurance is incomplete, so the check fails.`;
|
|
929
1050
|
}
|
|
930
1051
|
};
|
|
@@ -1139,7 +1260,8 @@ const gitHubReviewLayers = (target) => Layer.unwrap(Effect.gen(function* () {
|
|
|
1139
1260
|
token,
|
|
1140
1261
|
reviewAuthorLogin
|
|
1141
1262
|
});
|
|
1142
|
-
|
|
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)));
|
|
1143
1265
|
}));
|
|
1144
1266
|
//#endregion
|
|
1145
1267
|
//#region src/internal/providers.ts
|
|
@@ -1201,4 +1323,4 @@ const anthropicClientLayer = AnthropicClient.layerConfig({ apiKey: Config.redact
|
|
|
1201
1323
|
//#endregion
|
|
1202
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 };
|
|
1203
1325
|
|
|
1204
|
-
//# sourceMappingURL=providers-
|
|
1326
|
+
//# sourceMappingURL=providers-CODZQCmL.mjs.map
|