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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { $t as ReadFile, An as isReviewableFile, F as buildProfileMission, Ft as rankAndDedupeFindings, Gt as ListChangedFiles, H as toStoredFinding, I as computeProfileFingerprint, L as fromStoredConcern, O as ReviewState, On as commentableLines, Pt as planReviewUnits, Q as FanOutCoordinatorToolkitLayer, R as fromStoredFinding, Rt as CodeReview, V as toStoredConcern, Y as renderFingerprintMarker, an as ReviewToolkitLayer, cn as clampMaxFindings, ct as FileReviewUnitResult, d as gitHubReviewPublisherLayer, dn as makeReviewInstructions, en as ReadFileDiff, f as gitHubReviewRetirementHostLayer, it as FileReviewRequest, l as gitHubPriorReviewsLayer, ln as defaultReviewPolicy, mn as resolveGuidance, n as GitHubApiFailure, nn as ReviewFinding, nt as FileReviewDelegationFailure, o as PublishedReview, ot as FileReviewToolkitLayer, q as computeChangesetFingerprint, r as GitHubReviewTarget, rn as ReviewMission, s as ReviewPublisher, t as DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN, tn as ReviewConcern, u as gitHubPullRequestSourceLayer, vn as PullRequestMetadata, vt as fanOutHandlersLayerFor, w as ReviewExecutionContext, wt as makeFanOutReviewSuite, xn as ReviewInputViolation, yn as PullRequestSource, zt as FileDiffQuery } from "./github-DnenG3be.mjs";
1
+ import { An as ReviewConcern, F as buildProfileMission, Gn as resolveGuidance, H as toStoredFinding, Hn as makeReviewInstructions, Ht as reviewCandidateSubjectKey, I as computeProfileFingerprint, L as fromStoredConcern, Ln as clampMaxFindings, Mn as ReviewMission, Nt as fanOutHandlersLayerFor, O as ReviewState, On as ReadFile, Ot as assessmentSettlesSuggestionExactly, Pn as ReviewToolkitLayer, Qn as ReviewInputViolation, R as fromStoredFinding, Rn as defaultReviewPolicy, V as toStoredConcern, Xn as PullRequestSource, Y as renderFingerprintMarker, Yn as PullRequestMetadata, at as FileReviewDelegationFailure, cn as planReviewUnits, cr as isReviewableFile, d as gitHubReviewPublisherLayer, dt as FileReviewToolkitLayer, er as anchorViolation, f as gitHubReviewRetirementHostLayer, fn as CodeReview, jn as ReviewFinding, kn as ReadFileDiff, kt as confirmedFindingForPublication, l as gitHubPriorReviewsLayer, ln as rankAndDedupeFindings, lt as FileReviewRequest, mn as FileDiffView, n as GitHubApiFailure, o as PublishedReview, pn as FileDiffQuery, pt as FileReviewUnitResult, q as computeChangesetFingerprint, r as GitHubReviewTarget, s as ReviewPublisher, sn as findingAnchorInUnitEvidence, t as DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN, tt as FanOutCoordinatorToolkitLayer, u as gitHubPullRequestSourceLayer, w as ReviewExecutionContext, wt as ReviewCandidate, yn as ListChangedFiles, zt as makeFanOutReviewSuite } from "./github-BgtP7Rdv.mjs";
2
2
  import { Config, Context, DateTime, Effect, FileSystem, Layer, Option, Ref, Schema } from "effect";
3
3
  import { Agent, AgentPolicy, AgentRuntime, IdGenerator, SubagentReservationsMemoryLive, UsageBudgetLimits, UsageTotals, getToolExecutionClass, makeUsageBudget, toRunBudgetHook } from "effect-agent";
4
4
  import { Toolkit } from "effect/unstable/ai";
@@ -11,14 +11,57 @@ var FailedReviewUnit = class extends Schema.Class("@effect-agent/pr-review/Faile
11
11
  unitId: Schema.NonEmptyString.check(Schema.isMaxLength(32)),
12
12
  errorTag: Schema.NonEmptyString.check(Schema.isMaxLength(256))
13
13
  }) {};
14
+ /**
15
+ * Compatibility diagnostic retained for callers that consumed the original
16
+ * `coverage` field. New UI and state decisions use ReviewInputCoverage and
17
+ * ReviewAssurance directly.
18
+ */
14
19
  var ReviewCoverage = class extends Schema.Class("@effect-agent/pr-review/ReviewCoverage")({
15
20
  status: Schema.Literals(["complete", "incomplete"]),
16
21
  requiredPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
17
22
  reviewedPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
18
23
  unreviewedPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
19
24
  failedUnits: Schema.Array(FailedReviewUnit).check(Schema.isMaxLength(8)),
25
+ reasons: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(1e3))).check(Schema.isMaxLength(32))
26
+ }) {};
27
+ var ReviewInputCoverage = class extends Schema.Class("@effect-agent/pr-review/ReviewInputCoverage")({
28
+ status: Schema.Literals(["complete", "incomplete"]),
29
+ requiredPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
30
+ assignedPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
31
+ /** Assigned paths whose model-visible diff was truncated by the evidence bound. */
32
+ partialPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
33
+ unassignedPaths: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(512))).check(Schema.isMaxLength(300)),
20
34
  reasons: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(1e3))).check(Schema.isMaxLength(20))
21
35
  }) {};
36
+ var FailedReviewPass = class extends Schema.Class("@effect-agent/pr-review/FailedReviewPass")({
37
+ workId: Schema.NonEmptyString.check(Schema.isMaxLength(96)),
38
+ stage: Schema.Literals([
39
+ "discovery",
40
+ "specialist",
41
+ "verification"
42
+ ]),
43
+ errorTag: Schema.NonEmptyString.check(Schema.isMaxLength(256))
44
+ }) {};
45
+ var ReviewAssurance = class extends Schema.Class("@effect-agent/pr-review/ReviewAssurance")({
46
+ status: Schema.Literals([
47
+ "settled",
48
+ "incomplete",
49
+ "unverified"
50
+ ]),
51
+ requiredGeneralDiscoveryPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
52
+ completedGeneralDiscoveryPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
53
+ requiredSpecialistPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
54
+ completedSpecialistPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
55
+ requiredVerificationPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
56
+ completedVerificationPasses: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
57
+ discoveredCandidates: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
58
+ confirmedCandidates: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
59
+ rejectedCandidates: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
60
+ unsettledCandidates: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
61
+ /** Every failure remains visible within the coordinator's 32-call hard bound. */
62
+ failedPasses: Schema.Array(FailedReviewPass).check(Schema.isMaxLength(64)),
63
+ reasons: Schema.Array(Schema.NonEmptyString.check(Schema.isMaxLength(1e3))).check(Schema.isMaxLength(32))
64
+ }) {};
22
65
  const toolTrace = (events) => {
23
66
  const declared = /* @__PURE__ */ new Map();
24
67
  const succeeded = /* @__PURE__ */ new Map();
@@ -51,120 +94,347 @@ const boundedListReason = (label, values) => {
51
94
  }
52
95
  return rendered;
53
96
  };
54
- const flatCoverage = (files, totalFiles, trace) => {
97
+ const flatInputCoverage = (files, totalFiles, trace) => {
55
98
  const requiredPaths = sortedUnique(files.map((file) => file.path));
56
- const reviewed = /* @__PURE__ */ new Set();
99
+ const assigned = /* @__PURE__ */ new Set();
100
+ const partial = /* @__PURE__ */ new Set();
57
101
  const failedPaths = /* @__PURE__ */ new Set();
58
102
  for (const [toolCallId, declaration] of trace.declared) {
59
103
  if (declaration.toolName !== "read_file_diff") continue;
60
104
  const query = Schema.decodeUnknownOption(FileDiffQuery)(declaration.parameters);
61
105
  if (Option.isNone(query)) continue;
62
- if (trace.succeeded.has(toolCallId)) reviewed.add(query.value.path);
106
+ const success = trace.succeeded.get(toolCallId);
107
+ if (success !== void 0) {
108
+ assigned.add(query.value.path);
109
+ const view = Schema.decodeUnknownOption(FileDiffView)(success.result);
110
+ if (Option.isSome(view) && view.value.truncated) partial.add(query.value.path);
111
+ }
63
112
  if (trace.failed.has(toolCallId)) failedPaths.add(query.value.path);
64
113
  }
65
114
  const undiffable = files.filter((file) => !isReviewableFile(file)).map((file) => file.path);
66
- const unreviewed = requiredPaths.filter((path) => !reviewed.has(path) || undiffable.includes(path) || failedPaths.has(path));
115
+ const unassigned = requiredPaths.filter((path) => !assigned.has(path) || undiffable.includes(path) || failedPaths.has(path));
67
116
  const reasons = [];
68
117
  if (files.length < totalFiles) reasons.push(`review range exposed ${files.length} of ${totalFiles} required files`);
69
118
  if (undiffable.length > 0) reasons.push(boundedListReason("required paths have no reviewable diff or bounded text", undiffable));
70
119
  if (failedPaths.size > 0) reasons.push(boundedListReason("diff reads failed", failedPaths));
71
- if (unreviewed.length > 0) reasons.push(boundedListReason("required paths were not successfully reviewed", unreviewed));
72
- return ReviewCoverage.make({
120
+ if (partial.size > 0) reasons.push(boundedListReason("model-visible diff evidence was truncated", partial));
121
+ if (unassigned.length > 0) reasons.push(boundedListReason("required paths received no successful diff input", unassigned));
122
+ return ReviewInputCoverage.make({
73
123
  status: reasons.length === 0 ? "complete" : "incomplete",
74
124
  requiredPaths,
75
- reviewedPaths: sortedUnique(reviewed),
76
- unreviewedPaths: sortedUnique(unreviewed),
77
- failedUnits: [],
125
+ assignedPaths: sortedUnique(assigned),
126
+ partialPaths: sortedUnique(partial),
127
+ unassignedPaths: sortedUnique(unassigned),
78
128
  reasons
79
129
  });
80
130
  };
81
- const fanOutCoverage = (files, totalFiles, trace) => {
131
+ const fanOutInputCoverage = (files, totalFiles) => {
82
132
  const plan = planReviewUnits(files, { totalChangedFiles: totalFiles });
83
- const declarationsByUnit = /* @__PURE__ */ new Map();
84
- for (const [toolCallId, declaration] of trace.declared) {
85
- if (declaration.toolName !== "delegate_file_review") continue;
86
- const request = Schema.decodeUnknownOption(FileReviewRequest)(declaration.parameters);
87
- if (Option.isNone(request)) continue;
88
- const declarations = declarationsByUnit.get(request.value.unitId) ?? [];
89
- declarations.push({
90
- id: toolCallId,
91
- paths: request.value.paths
92
- });
93
- declarationsByUnit.set(request.value.unitId, declarations);
94
- }
95
- const reviewed = /* @__PURE__ */ new Set();
96
- const unreviewed = /* @__PURE__ */ new Set([...plan.undiffablePaths, ...plan.unassignedPaths]);
97
- const failedUnits = [];
133
+ const assignedPaths = sortedUnique(plan.units.flatMap((unit) => unit.paths));
134
+ const unassignedPaths = sortedUnique([...plan.undiffablePaths, ...plan.unassignedPaths]);
98
135
  const reasons = [];
99
- for (const unit of plan.units) {
100
- const declarations = declarationsByUnit.get(unit.unitId) ?? [];
101
- const expectedPaths = [...unit.paths];
102
- const exact = declarations.filter((declaration) => declaration.paths.length === expectedPaths.length && declaration.paths.every((path, index) => path === expectedPaths[index]));
103
- const successful = exact.filter((declaration) => {
104
- const event = trace.succeeded.get(declaration.id);
105
- if (event === void 0 || trace.failed.has(declaration.id)) return false;
106
- const result = Schema.decodeUnknownOption(FileReviewUnitResult)(event.result);
107
- return Option.isSome(result) && result.value.unitId === unit.unitId;
108
- });
109
- if (declarations.length === 1 && exact.length === 1 && successful.length === 1) {
110
- for (const path of unit.paths) reviewed.add(path);
111
- continue;
112
- }
113
- for (const path of unit.paths) unreviewed.add(path);
114
- const failure = declarations.map((declaration) => trace.failed.get(declaration.id)).find((event) => event !== void 0);
115
- const returnedFailure = declarations.map((declaration) => trace.succeeded.get(declaration.id)).filter((event) => event !== void 0).map((event) => Schema.decodeUnknownOption(FileReviewDelegationFailure)(event.result)).find(Option.isSome);
116
- failedUnits.push(FailedReviewUnit.make({
117
- unitId: unit.unitId,
118
- errorTag: failure?.errorTag ?? (returnedFailure !== void 0 ? returnedFailure.value._tag === "FileReviewUnitFailed" ? `${returnedFailure.value._tag}:${returnedFailure.value.childErrorTag}` : returnedFailure.value._tag : void 0) ?? (declarations.length === 0 ? "UnitNotAssigned" : declarations.length > 1 ? "UnitAssignedMultipleTimes" : exact.length === 0 ? "UnitAssignmentMismatch" : "UnitDidNotSettleSuccessfully")
119
- }));
120
- }
121
136
  if (plan.truncated) reasons.push(`review range exposed ${files.length} of ${totalFiles} required files`);
122
137
  if (plan.undiffablePaths.length > 0) reasons.push(boundedListReason("required paths have no reviewable diff or bounded text", plan.undiffablePaths));
138
+ if (plan.partialEvidencePaths.length > 0) reasons.push(boundedListReason("fan-out capacity left some deterministic evidence shards unassigned", plan.partialEvidencePaths));
139
+ if (plan.unassignedEvidenceShardCount > 0) {
140
+ reasons.push(`${plan.unassignedEvidenceShardCount} deterministic evidence shard(s) exceeded fan-out capacity`);
141
+ reasons.push(boundedListReason(`unassigned evidence shard identifier sample (${plan.unassignedEvidenceShardIds.length} of ${plan.unassignedEvidenceShardCount})`, plan.unassignedEvidenceShardIds));
142
+ }
123
143
  if (plan.unassignedPaths.length > 0) reasons.push(boundedListReason("fan-out capacity left paths unassigned", plan.unassignedPaths));
124
- if (failedUnits.length > 0) reasons.push(boundedListReason("review units did not complete", failedUnits.map((unit) => `${unit.unitId} (${unit.errorTag})`)));
125
- return ReviewCoverage.make({
144
+ return ReviewInputCoverage.make({
126
145
  status: reasons.length === 0 ? "complete" : "incomplete",
127
146
  requiredPaths: sortedUnique(files.map((file) => file.path)),
128
- reviewedPaths: sortedUnique(reviewed),
129
- unreviewedPaths: sortedUnique(unreviewed),
130
- failedUnits,
147
+ assignedPaths,
148
+ partialPaths: plan.partialEvidencePaths,
149
+ unassignedPaths,
131
150
  reasons
132
151
  });
133
152
  };
134
- /**
135
- * Host-verified per-file summaries from the fan-out run's Tool events: for
136
- * every successfully settled delegation, the child-reported `fileSummaries`
137
- * whose paths belong to that invocation's requested unit. This is the
138
- * declassification check `projectResult` cannot perform itself (it never sees
139
- * the request): a child assigned file A cannot smuggle a summary for changed
140
- * file B into the merged walkthrough, and a coordinator cannot invent or edit
141
- * entries only exact child-reported, in-unit summaries survive.
142
- */
143
- const collectUnitFileSummaries = (events) => {
144
- const trace = toolTrace(events);
145
- const entries = [];
146
- for (const [toolCallId, declaration] of trace.declared) {
147
- if (declaration.toolName !== "delegate_file_review") continue;
153
+ const sameStrings = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
154
+ const candidateKey = (candidate) => JSON.stringify(Schema.encodeSync(ReviewCandidate)(candidate));
155
+ const sameCandidates = (left, right) => left.length === right.length && left.every((candidate, index) => {
156
+ const corresponding = right[index];
157
+ return corresponding !== void 0 && candidateKey(candidate) === candidateKey(corresponding);
158
+ });
159
+ const delegationDeclarations = (trace) => [...trace.declared].flatMap(([id, declaration]) => {
160
+ if (declaration.toolName !== "delegate_file_review") return [];
161
+ const request = Schema.decodeUnknownOption(FileReviewRequest)(declaration.parameters);
162
+ return Option.isNone(request) ? [] : [{
163
+ id,
164
+ request: request.value
165
+ }];
166
+ });
167
+ const failureTag = (trace, id) => {
168
+ const failed = trace.failed.get(id);
169
+ if (failed !== void 0) return failed.errorTag;
170
+ const succeeded = trace.succeeded.get(id);
171
+ if (succeeded === void 0) return void 0;
172
+ const returned = Schema.decodeUnknownOption(FileReviewDelegationFailure)(succeeded.result);
173
+ if (Option.isNone(returned)) return void 0;
174
+ return returned.value._tag === "FileReviewUnitFailed" ? `${returned.value._tag}:${returned.value.childErrorTag}` : returned.value._tag;
175
+ };
176
+ const exactDiscoveryRequest = (request, pass) => request.phase === "discovery" && request.workId === pass.passId && request.unitId === pass.unitId && request.perspective === pass.perspective && sameStrings(request.paths, pass.paths) && sameStrings(request.evidenceShardIds, pass.evidenceShardIds) && sameStrings(request.riskCategories, pass.riskCategories) && request.candidates.length === 0;
177
+ const validCandidate = (candidate, pass, unit, files, anchorFiles) => {
178
+ const allowed = new Set(pass.paths);
179
+ const kind = candidate._tag === "FindingCandidate" ? "finding" : "concern";
180
+ const idPrefix = `${pass.passId}:${kind}:`;
181
+ return candidate.candidateId.startsWith(idPrefix) && /^\d{3}$/.test(candidate.candidateId.slice(idPrefix.length)) && candidate.workId === pass.passId && candidate.unitId === pass.unitId && candidate.evidencePaths.length > 0 && candidate.evidencePaths.every((path) => allowed.has(path)) && (candidate._tag !== "FindingCandidate" || allowed.has(candidate.finding.path) && anchorViolation(candidate.finding, anchorFiles) === void 0 && findingAnchorInUnitEvidence(candidate.finding, unit, files));
182
+ };
183
+ const flatAssurance = () => ({
184
+ assurance: ReviewAssurance.make({
185
+ status: "unverified",
186
+ requiredGeneralDiscoveryPasses: 1,
187
+ completedGeneralDiscoveryPasses: 1,
188
+ requiredSpecialistPasses: 0,
189
+ completedSpecialistPasses: 0,
190
+ requiredVerificationPasses: 1,
191
+ completedVerificationPasses: 0,
192
+ discoveredCandidates: 0,
193
+ confirmedCandidates: 0,
194
+ rejectedCandidates: 0,
195
+ unsettledCandidates: 0,
196
+ failedPasses: [],
197
+ reasons: ["flat review has no independent candidate-verification pass; use the fan-out pipeline for a settled assurance result"]
198
+ }),
199
+ confirmedFindings: [],
200
+ confirmedConcerns: [],
201
+ walkthrough: []
202
+ });
203
+ const fanOutAssurance = (files, totalFiles, anchorFiles, trace) => {
204
+ const plan = planReviewUnits(files, { totalChangedFiles: totalFiles });
205
+ const declarations = delegationDeclarations(trace);
206
+ const consumedDeclarationIds = /* @__PURE__ */ new Set();
207
+ const failedPasses = [];
208
+ const reasons = [];
209
+ const candidatesByUnit = /* @__PURE__ */ new Map();
210
+ const walkthrough = [];
211
+ let completedGeneralDiscoveryPasses = 0;
212
+ let completedSpecialistPasses = 0;
213
+ for (const pass of plan.discoveryPasses) {
214
+ const unit = plan.units.find((candidate) => candidate.unitId === pass.unitId);
215
+ const matching = declarations.filter(({ request }) => exactDiscoveryRequest(request, pass));
216
+ const stage = pass.perspective === "risk-specialist" ? "specialist" : "discovery";
217
+ if (matching.length !== 1) {
218
+ failedPasses.push(FailedReviewPass.make({
219
+ workId: pass.passId,
220
+ stage,
221
+ errorTag: matching.length === 0 ? "PassNotAssigned" : "PassAssignedMultipleTimes"
222
+ }));
223
+ continue;
224
+ }
225
+ const call = matching[0];
226
+ if (call === void 0) {
227
+ failedPasses.push(FailedReviewPass.make({
228
+ workId: pass.passId,
229
+ stage,
230
+ errorTag: "PassLookupInvariantFailed"
231
+ }));
232
+ continue;
233
+ }
234
+ consumedDeclarationIds.add(call.id);
235
+ const failure = failureTag(trace, call.id);
236
+ const succeeded = trace.succeeded.get(call.id);
237
+ const result = succeeded === void 0 ? Option.none() : Schema.decodeUnknownOption(FileReviewUnitResult)(succeeded.result);
238
+ if (failure !== void 0 || Option.isNone(result) || result.value.phase !== "discovery" || result.value.workId !== pass.passId || result.value.unitId !== pass.unitId || result.value.assessments.length !== 0) {
239
+ failedPasses.push(FailedReviewPass.make({
240
+ workId: pass.passId,
241
+ stage,
242
+ errorTag: failure ?? "DiscoveryDidNotSettleExactly"
243
+ }));
244
+ continue;
245
+ }
246
+ const ids = /* @__PURE__ */ new Set();
247
+ let candidatesValid = true;
248
+ for (const candidate of result.value.candidates) {
249
+ if (unit === void 0 || ids.has(candidate.candidateId) || !validCandidate(candidate, pass, unit, files, anchorFiles)) {
250
+ candidatesValid = false;
251
+ break;
252
+ }
253
+ ids.add(candidate.candidateId);
254
+ }
255
+ const unitCandidates = candidatesByUnit.get(pass.unitId) ?? [];
256
+ const unitIds = new Set(unitCandidates.map((candidate) => candidate.candidateId));
257
+ if (result.value.candidates.some((candidate) => unitIds.has(candidate.candidateId))) candidatesValid = false;
258
+ if (!candidatesValid) {
259
+ failedPasses.push(FailedReviewPass.make({
260
+ workId: pass.passId,
261
+ stage,
262
+ errorTag: "DiscoveryCandidateMismatch"
263
+ }));
264
+ continue;
265
+ }
266
+ if (stage === "specialist") completedSpecialistPasses += 1;
267
+ else completedGeneralDiscoveryPasses += 1;
268
+ const subjectKeys = new Set(unitCandidates.map(reviewCandidateSubjectKey));
269
+ for (const candidate of result.value.candidates) {
270
+ const subjectKey = reviewCandidateSubjectKey(candidate);
271
+ if (subjectKeys.has(subjectKey)) continue;
272
+ subjectKeys.add(subjectKey);
273
+ unitCandidates.push(candidate);
274
+ }
275
+ candidatesByUnit.set(pass.unitId, unitCandidates);
276
+ if (pass.perspective === "general") {
277
+ const allowed = new Set(pass.paths);
278
+ walkthrough.push(...result.value.fileSummaries.filter((entry) => allowed.has(entry.path)));
279
+ }
280
+ }
281
+ const confirmedCandidates = [];
282
+ let rejectedCandidates = 0;
283
+ let unsettledCandidates = 0;
284
+ let requiredVerificationPasses = 0;
285
+ let completedVerificationPasses = 0;
286
+ for (const unit of plan.units) {
287
+ const candidates = candidatesByUnit.get(unit.unitId) ?? [];
288
+ if (candidates.length === 0) continue;
289
+ requiredVerificationPasses += 1;
290
+ const workId = `${unit.unitId}-verification`;
291
+ const matching = declarations.filter(({ request }) => request.phase === "verification" && request.workId === workId && request.unitId === unit.unitId && request.perspective === "candidate-verification" && sameStrings(request.paths, unit.paths) && sameStrings(request.evidenceShardIds, unit.evidenceShards.map((shard) => shard.shardId)) && sameStrings(request.riskCategories, unit.riskCategories) && sameCandidates(request.candidates, candidates));
292
+ if (matching.length !== 1) {
293
+ unsettledCandidates += candidates.length;
294
+ failedPasses.push(FailedReviewPass.make({
295
+ workId,
296
+ stage: "verification",
297
+ errorTag: matching.length === 0 ? "VerificationNotAssignedOrCandidateMismatch" : "VerificationAssignedMultipleTimes"
298
+ }));
299
+ continue;
300
+ }
301
+ const call = matching[0];
302
+ if (call === void 0) {
303
+ unsettledCandidates += candidates.length;
304
+ failedPasses.push(FailedReviewPass.make({
305
+ workId,
306
+ stage: "verification",
307
+ errorTag: "PassLookupInvariantFailed"
308
+ }));
309
+ continue;
310
+ }
311
+ consumedDeclarationIds.add(call.id);
312
+ const failure = failureTag(trace, call.id);
313
+ const succeeded = trace.succeeded.get(call.id);
314
+ const result = succeeded === void 0 ? Option.none() : Schema.decodeUnknownOption(FileReviewUnitResult)(succeeded.result);
315
+ if (failure !== void 0 || Option.isNone(result) || result.value.phase !== "verification" || result.value.workId !== workId || result.value.unitId !== unit.unitId || result.value.candidates.length !== 0 || result.value.fileSummaries.length !== 0) {
316
+ unsettledCandidates += candidates.length;
317
+ failedPasses.push(FailedReviewPass.make({
318
+ workId,
319
+ stage: "verification",
320
+ errorTag: failure ?? "VerificationDidNotSettleExactly"
321
+ }));
322
+ continue;
323
+ }
324
+ const byId = new Map(candidates.map((candidate) => [candidate.candidateId, candidate]));
325
+ const assessedIds = /* @__PURE__ */ new Set();
326
+ let suggestionSettlementExact = true;
327
+ const exactAssessments = result.value.assessments.every((assessment) => {
328
+ const candidate = byId.get(assessment.candidateId);
329
+ if (candidate === void 0 || assessedIds.has(assessment.candidateId)) return false;
330
+ assessedIds.add(assessment.candidateId);
331
+ if (!assessmentSettlesSuggestionExactly(assessment, candidate)) suggestionSettlementExact = false;
332
+ return true;
333
+ });
334
+ if (byId.size !== candidates.length || !exactAssessments || assessedIds.size !== byId.size || !suggestionSettlementExact) {
335
+ unsettledCandidates += candidates.length;
336
+ failedPasses.push(FailedReviewPass.make({
337
+ workId,
338
+ stage: "verification",
339
+ errorTag: exactAssessments && !suggestionSettlementExact ? "SuggestionSettlementMismatch" : "VerificationAssessmentMismatch"
340
+ }));
341
+ continue;
342
+ }
343
+ completedVerificationPasses += 1;
344
+ for (const assessment of result.value.assessments) if (assessment.disposition === "confirmed") {
345
+ const candidate = byId.get(assessment.candidateId);
346
+ if (candidate !== void 0) confirmedCandidates.push({
347
+ assessment,
348
+ candidate
349
+ });
350
+ } else rejectedCandidates += 1;
351
+ }
352
+ const unexpected = [...trace.declared].filter(([id, declaration]) => declaration.toolName === "delegate_file_review" && !consumedDeclarationIds.has(id));
353
+ for (const [, declaration] of unexpected) {
148
354
  const request = Schema.decodeUnknownOption(FileReviewRequest)(declaration.parameters);
149
- if (Option.isNone(request)) continue;
150
- const success = trace.succeeded.get(toolCallId);
151
- if (success === void 0 || trace.failed.has(toolCallId)) continue;
152
- const result = Schema.decodeUnknownOption(FileReviewUnitResult)(success.result);
153
- if (Option.isNone(result) || result.value.unitId !== request.value.unitId) continue;
154
- const assigned = new Set(request.value.paths);
155
- for (const entry of result.value.fileSummaries ?? []) if (assigned.has(entry.path)) entries.push(entry);
355
+ failedPasses.push(FailedReviewPass.make({
356
+ workId: Option.isSome(request) ? request.value.workId : "invalid-delegation-request",
357
+ stage: Option.isSome(request) && request.value.phase === "verification" ? "verification" : "discovery",
358
+ errorTag: "UnexpectedPass"
359
+ }));
156
360
  }
157
- return entries;
361
+ if (failedPasses.length > 0) reasons.push(boundedListReason("configured review passes did not settle", failedPasses.map((pass) => `${pass.workId} (${pass.errorTag})`)));
362
+ if (unsettledCandidates > 0) reasons.push(`${unsettledCandidates} discovered candidate(s) did not receive exact verification`);
363
+ const requiredSpecialistPasses = plan.discoveryPasses.filter((pass) => pass.perspective === "risk-specialist").length;
364
+ const requiredGeneralDiscoveryPasses = plan.discoveryPasses.length - requiredSpecialistPasses;
365
+ const discoveredCandidates = [...candidatesByUnit.values()].reduce((total, candidates) => total + candidates.length, 0);
366
+ return {
367
+ assurance: ReviewAssurance.make({
368
+ status: reasons.length === 0 ? "settled" : "incomplete",
369
+ requiredGeneralDiscoveryPasses,
370
+ completedGeneralDiscoveryPasses,
371
+ requiredSpecialistPasses,
372
+ completedSpecialistPasses,
373
+ requiredVerificationPasses,
374
+ completedVerificationPasses,
375
+ discoveredCandidates,
376
+ confirmedCandidates: confirmedCandidates.length,
377
+ rejectedCandidates,
378
+ unsettledCandidates,
379
+ failedPasses,
380
+ reasons
381
+ }),
382
+ confirmedFindings: confirmedCandidates.flatMap(({ assessment, candidate }) => candidate._tag === "FindingCandidate" ? [confirmedFindingForPublication(assessment, candidate)] : []),
383
+ confirmedConcerns: confirmedCandidates.flatMap(({ candidate }) => candidate._tag === "ConcernCandidate" ? [candidate.concern] : []),
384
+ walkthrough
385
+ };
158
386
  };
159
- /** Assess one settled run without trusting its prose summary or verdict. */
160
- const assessReviewCoverage = (input) => {
161
- const trace = toolTrace(input.events);
162
- const coverage = input.shape === "fan-out" ? fanOutCoverage(input.files, input.totalFiles, trace) : flatCoverage(input.files, input.totalFiles, trace);
163
- if (input.anchorFiles.length >= input.totalAnchorFiles) return coverage;
387
+ const compatibilityCoverage = (inputCoverage, assurance) => {
388
+ const assuranceIncomplete = assurance.status === "incomplete";
389
+ const failedUnits = /* @__PURE__ */ new Map();
390
+ for (const pass of assurance.failedPasses) {
391
+ const unitId = pass.workId.slice(0, 8);
392
+ if (!failedUnits.has(unitId)) failedUnits.set(unitId, FailedReviewUnit.make({
393
+ unitId,
394
+ errorTag: `${pass.stage}:${pass.errorTag}`
395
+ }));
396
+ }
164
397
  return ReviewCoverage.make({
165
- ...coverage,
398
+ status: inputCoverage.status === "complete" && !assuranceIncomplete ? "complete" : "incomplete",
399
+ requiredPaths: inputCoverage.requiredPaths,
400
+ reviewedPaths: inputCoverage.assignedPaths,
401
+ unreviewedPaths: sortedUnique([...inputCoverage.partialPaths, ...inputCoverage.unassignedPaths]),
402
+ failedUnits: [...failedUnits.values()].slice(0, 8),
403
+ reasons: [...inputCoverage.reasons, ...assuranceIncomplete ? assurance.reasons : []]
404
+ });
405
+ };
406
+ /** Assess one settled run without trusting coordinator prose or findings. */
407
+ const assessReviewPipeline = (input) => {
408
+ const trace = toolTrace(input.events);
409
+ let inputCoverage = input.shape === "fan-out" ? fanOutInputCoverage(input.files, input.totalFiles) : flatInputCoverage(input.files, input.totalFiles, trace);
410
+ if (input.anchorFiles.length < input.totalAnchorFiles) inputCoverage = ReviewInputCoverage.make({
411
+ ...inputCoverage,
166
412
  status: "incomplete",
167
- reasons: [...coverage.reasons, `full pull-request anchor surface exposed ${input.anchorFiles.length} of ${input.totalAnchorFiles} required files`]
413
+ reasons: [...inputCoverage.reasons, `full pull-request anchor surface exposed ${input.anchorFiles.length} of ${input.totalAnchorFiles} required files`]
414
+ });
415
+ const assessed = input.shape === "fan-out" ? fanOutAssurance(input.files, input.totalFiles, input.anchorFiles, trace) : flatAssurance();
416
+ return {
417
+ inputCoverage,
418
+ assurance: assessed.assurance,
419
+ coverage: compatibilityCoverage(inputCoverage, assessed.assurance),
420
+ confirmedFindings: assessed.confirmedFindings,
421
+ confirmedConcerns: assessed.confirmedConcerns,
422
+ walkthrough: assessed.walkthrough
423
+ };
424
+ };
425
+ /** Compatibility helper; prefer assessReviewPipeline for precise claims. */
426
+ const assessReviewCoverage = (input) => assessReviewPipeline(input).coverage;
427
+ /** Host-verified summaries from successful general discovery passes only. */
428
+ const collectUnitFileSummaries = (events) => {
429
+ const trace = toolTrace(events);
430
+ return delegationDeclarations(trace).flatMap(({ id, request }) => {
431
+ if (request.phase !== "discovery" || request.perspective !== "general") return [];
432
+ const success = trace.succeeded.get(id);
433
+ if (success === void 0 || trace.failed.has(id)) return [];
434
+ const result = Schema.decodeUnknownOption(FileReviewUnitResult)(success.result);
435
+ if (Option.isNone(result) || result.value.phase !== "discovery" || result.value.workId !== request.workId || result.value.unitId !== request.unitId) return [];
436
+ const assigned = new Set(request.paths);
437
+ return result.value.fileSummaries.filter((entry) => assigned.has(entry.path));
168
438
  });
169
439
  };
170
440
  //#endregion
@@ -396,7 +666,8 @@ const severityCounts = (review, carriedFindings = [], carriedConcerns = []) => {
396
666
  */
397
667
  const renderVerdictCallout = (review, options) => {
398
668
  const counts = severityCounts(review, options.carriedFindings, options.carriedConcerns);
399
- if (options.coverage?.status === "incomplete") return `> [!CAUTION]\n> Review coverage is incomplete — the check must not pass.${counts.blocking > 0 ? ` It also has ${countNoun(counts.blocking, "blocking finding")}.` : ""}`;
669
+ if (options.inputCoverage?.status === "incomplete" || options.inputCoverage === void 0 && options.coverage?.status === "incomplete") return `> [!CAUTION]\n> Input coverage is incomplete — the check must not pass.${counts.blocking > 0 ? ` It also has ${countNoun(counts.blocking, "blocking finding")}.` : ""}`;
670
+ if (options.assurance !== void 0 && options.assurance.status !== "settled") return `> [!CAUTION]\n> Configured review assurance did not settle — the check must not pass.${counts.blocking > 0 ? ` It also has ${countNoun(counts.blocking, "blocking finding")}.` : ""}`;
400
671
  if (counts.blocking > 0) return `> [!CAUTION]\n> ${countNoun(counts.blocking, "blocking finding")} — do not merge before addressing ${counts.blocking === 1 ? "it" : "them"}.`;
401
672
  if (counts.important > 0) return `> [!IMPORTANT]\n> ${countNoun(counts.important, "important finding")} to address before merging.`;
402
673
  if (counts.total > 0) return "> ℹ️ Minor suggestions only — mergeable as-is.";
@@ -492,15 +763,6 @@ const renderReviewMetadata = (options) => [
492
763
  * Why one finding cannot become an inline comment, or undefined when it can.
493
764
  * Exported so tests can pin each rule individually.
494
765
  */
495
- const anchorViolation = (finding, files) => {
496
- const file = files.find((candidate) => candidate.path === finding.path);
497
- if (file === void 0) return "path is not part of the changeset";
498
- if (file.patch === void 0) return "file has no anchorable textual diff";
499
- if (finding.endLine < finding.startLine) return "endLine precedes startLine";
500
- if (finding.endLine - finding.startLine + 1 > 100) return "range is implausibly large";
501
- const anchors = commentableLines(file.patch);
502
- for (let line = finding.startLine; line <= finding.endLine; line += 1) if (!anchors.has(line)) return `line ${line} is not part of the diff`;
503
- };
504
766
  /**
505
767
  * Turn one validated review into the exact GitHub publication payload.
506
768
  * `applyVerdict: false` (the safe default) always posts a COMMENT review;
@@ -548,22 +810,27 @@ const planPublication = (review, files, options) => {
548
810
  const parts = [renderVerdictCallout(review, {
549
811
  carriedFindings,
550
812
  carriedConcerns,
551
- coverage: options.coverage
813
+ coverage: options.coverage,
814
+ inputCoverage: options.inputCoverage,
815
+ assurance: options.assurance
552
816
  })];
553
- if (options.reviewMode !== void 0 && options.reviewReason !== void 0) parts.push("", options.reviewMode === "incremental" ? `**Incremental scope:** reviewed ${options.reviewFilesVisible ?? files.length} file(s) ${options.reviewReason}. Unchanged accepted scope was preserved and not reopened.` : `**Full-diff scope:** ${options.reviewReason}.`);
817
+ if (options.reviewMode !== void 0 && options.reviewReason !== void 0) parts.push("", options.reviewMode === "incremental" ? `**Incremental scope:** reopened ${options.reviewFilesVisible ?? files.length} affected file(s) ${options.reviewReason}. Unchanged settled scope was preserved and not reopened.` : `**Full-diff scope:** ${options.reviewReason}.`);
554
818
  if (options.stateNotice !== void 0) parts.push("", `⚠️ Continuity state was not stored (${options.stateNotice.slice(0, 1e3)}); the next run will safely review the full diff.`);
555
819
  parts.push("", renderReviewStats(files, options.totalChangedFiles, counts));
820
+ if (options.inputCoverage !== void 0 && options.assurance !== void 0) parts.push("", `**Input coverage:** ${options.inputCoverage.status} (${options.inputCoverage.assignedPaths.length}/${options.inputCoverage.requiredPaths.length} paths assigned, ${options.inputCoverage.partialPaths.length} partial) · **Review assurance:** ${options.assurance.status} (${options.assurance.completedGeneralDiscoveryPasses}/${options.assurance.requiredGeneralDiscoveryPasses} general discovery, ${options.assurance.completedSpecialistPasses}/${options.assurance.requiredSpecialistPasses} specialist, ${options.assurance.completedVerificationPasses}/${options.assurance.requiredVerificationPasses} verification; ${options.assurance.confirmedCandidates} confirmed / ${options.assurance.rejectedCandidates} rejected / ${options.assurance.unsettledCandidates} unsettled candidates)`);
556
821
  parts.push("", review.summary);
557
822
  if (walkthroughKept && walkthrough.length > 0) parts.push("", renderWalkthrough(walkthrough));
558
823
  else if (walkthrough.length > 0) parts.push("", "⚠️ Walkthrough omitted — the body exceeded GitHub's review size cap.");
559
- if (options.coverage?.status === "incomplete") parts.push("", "### 🛑 Incomplete coverage", "", ...options.coverage.reasons.map((reason) => `- ${reason}`));
824
+ if (options.inputCoverage?.status === "incomplete") parts.push("", "### 🛑 Incomplete input coverage", "", ...options.inputCoverage.reasons.map((reason) => `- ${reason}`));
825
+ else if (options.inputCoverage === void 0 && options.coverage?.status === "incomplete") parts.push("", "### 🛑 Incomplete coverage", "", ...options.coverage.reasons.map((reason) => `- ${reason}`));
826
+ if (options.assurance !== void 0 && options.assurance.status !== "settled") parts.push("", "### 🛑 Incomplete review assurance", "", ...options.assurance.reasons.map((reason) => `- ${reason}`));
560
827
  if (carriedFindings.length > 0) parts.push("", "<details>", `<summary>Unresolved findings carried from unchanged scope (${carriedFindings.length})</summary>`, "", ...carriedFindings.map(renderCarriedFinding), "", "</details>");
561
828
  if (carriedConcerns.length > 0) {
562
829
  parts.push("", "### Unresolved concerns carried to the final audit");
563
830
  for (const concern of carriedConcerns) parts.push("", renderConcern(concern));
564
831
  }
565
832
  for (const concern of sortedConcerns.slice(0, concernsKept)) parts.push("", renderConcern(concern));
566
- if (files.length < options.totalChangedFiles) parts.push("", `⚠️ Reviewed ${files.length} of ${options.totalChangedFiles} changed files — the changeset exceeded the reviewer's file bound.`);
833
+ if (files.length < options.totalChangedFiles) parts.push("", `⚠️ Input exposed ${files.length} of ${options.totalChangedFiles} changed files — the changeset exceeded the reviewer's file bound.`);
567
834
  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>");
568
835
  if (consolidatedPromptWanted) parts.push("", promptsKept ? renderConsolidatedAgentPrompt(promptEntries) : "⚠️ Consolidated agent prompt omitted — the body exceeded GitHub's review size cap.");
569
836
  if (omitted > 0) parts.push("", `⚠️ ${countNoun(omitted, "review item")} omitted — the body exceeded GitHub's review size cap.`);
@@ -571,7 +838,7 @@ const planPublication = (review, files, options) => {
571
838
  return parts.join("\n");
572
839
  };
573
840
  const counts = severityCounts(review, options.carriedFindings ?? [], options.carriedConcerns ?? []);
574
- const event = !options.applyVerdict ? "COMMENT" : options.coverage?.status === "incomplete" || counts.blocking > 0 ? "REQUEST_CHANGES" : review.verdict === "approve" && counts.important === 0 ? "APPROVE" : "COMMENT";
841
+ const event = !options.applyVerdict ? "COMMENT" : options.inputCoverage?.status === "incomplete" || options.inputCoverage === void 0 && options.coverage?.status === "incomplete" || options.assurance !== void 0 && options.assurance.status !== "settled" || counts.blocking > 0 ? "REQUEST_CHANGES" : review.verdict === "approve" && counts.important === 0 ? "APPROVE" : "COMMENT";
575
842
  const tail = [
576
843
  renderReviewMetadata({
577
844
  headSha: options.headSha,
@@ -636,11 +903,11 @@ const reviewBudgetLimits = UsageBudgetLimits.make({
636
903
  * while bounded children run.
637
904
  */
638
905
  const fanOutReviewBudgetLimits = UsageBudgetLimits.make({
639
- maxInputTokens: 4e5,
640
- maxOutputTokens: 16e3,
641
- maxToolCalls: 24,
906
+ maxInputTokens: 6e5,
907
+ maxOutputTokens: 32e3,
908
+ maxToolCalls: 32,
642
909
  maxCostMicrousd: 2e6,
643
- maxDurationMillis: 9e5
910
+ maxDurationMillis: 12e5
644
911
  });
645
912
  /** Everything one review run produced, publication receipt included. */
646
913
  var ReviewRunOutcome = class extends Schema.Class("@effect-agent/pr-review/ReviewRunOutcome")({
@@ -651,6 +918,10 @@ var ReviewRunOutcome = class extends Schema.Class("@effect-agent/pr-review/Revie
651
918
  activeConcerns: Schema.Array(ReviewConcern).check(Schema.isMaxLength(10)),
652
919
  /** Host-owned structural coverage used by the Actions check conclusion. */
653
920
  coverage: ReviewCoverage,
921
+ /** Exact path/evidence assignment, distinct from semantic review work. */
922
+ inputCoverage: ReviewInputCoverage,
923
+ /** Settlement of configured discovery, specialist, and verification work. */
924
+ assurance: ReviewAssurance,
654
925
  plan: ReviewPublicationPlan,
655
926
  published: Schema.optionalKey(PublishedReview),
656
927
  turns: Schema.Int.check(Schema.isGreaterThan(0)),
@@ -728,17 +999,22 @@ const executeReview = (binding, options) => Effect.gen(function* () {
728
999
  const result = yield* detached.await;
729
1000
  const events = yield* detached.events;
730
1001
  const decoded = yield* Schema.decodeUnknownEffect(CodeReview)(result.output);
731
- const verifiedReview = options.reviewShape !== "fan-out" || decoded.walkthrough === void 0 ? decoded : (() => {
732
- const verified = new Set(collectUnitFileSummaries(events).map((entry) => `${entry.path}\u0000${entry.summary}`));
733
- const walkthrough = decoded.walkthrough.filter((entry) => verified.has(`${entry.path}\u0000${entry.summary}`));
734
- return CodeReview.make({
735
- summary: decoded.summary,
736
- verdict: decoded.verdict,
737
- findings: decoded.findings,
738
- ...decoded.concerns !== void 0 ? { concerns: decoded.concerns } : {},
739
- ...walkthrough.length > 0 ? { walkthrough } : {}
740
- });
741
- })();
1002
+ const reviewTotalFiles = executionContext?.totalFiles ?? metadata.totalChangedFiles;
1003
+ const pipeline = assessReviewPipeline({
1004
+ shape: options.reviewShape ?? "flat",
1005
+ files,
1006
+ totalFiles: reviewTotalFiles,
1007
+ anchorFiles,
1008
+ totalAnchorFiles: metadata.totalChangedFiles,
1009
+ events
1010
+ });
1011
+ const verifiedReview = options.reviewShape !== "fan-out" ? decoded : CodeReview.make({
1012
+ summary: decoded.summary,
1013
+ verdict: decoded.verdict,
1014
+ findings: rankAndDedupeFindings(pipeline.confirmedFindings),
1015
+ ...pipeline.confirmedConcerns.length === 0 ? {} : { concerns: rankAndDedupeConcerns(pipeline.confirmedConcerns) },
1016
+ ...pipeline.walkthrough.length === 0 ? {} : { walkthrough: pipeline.walkthrough }
1017
+ });
742
1018
  const review = enforceFindingsBound(verifiedReview, clampMaxFindings(options.maxFindings));
743
1019
  const usage = yield* budget.snapshot;
744
1020
  const affectedPaths = new Set(executionContext?.affectedPaths ?? files.flatMap((file) => file.previousPath === void 0 ? [file.path] : [file.path, file.previousPath]));
@@ -756,16 +1032,8 @@ const executeReview = (binding, options) => Effect.gen(function* () {
756
1032
  const key = `${concern.title}\u0000${concern.body}`;
757
1033
  return activeConcernKeys.has(key) && !currentConcernKeys.has(key);
758
1034
  });
759
- const reviewTotalFiles = executionContext?.totalFiles ?? metadata.totalChangedFiles;
760
- const coverage = assessReviewCoverage({
761
- shape: options.reviewShape ?? "flat",
762
- files,
763
- totalFiles: reviewTotalFiles,
764
- anchorFiles,
765
- totalAnchorFiles: metadata.totalChangedFiles,
766
- events
767
- });
768
- const stateCandidate = executionContext !== void 0 && coverage.status === "complete" && fingerprint !== void 0 && metadata.baseSha !== void 0 && executionContext.stateAuthenticator?.status === "available" ? ReviewState.make({
1035
+ const { assurance, coverage, inputCoverage } = pipeline;
1036
+ const stateCandidate = executionContext !== void 0 && inputCoverage.status === "complete" && assurance.status === "settled" && fingerprint !== void 0 && metadata.baseSha !== void 0 && executionContext.stateAuthenticator?.status === "available" ? ReviewState.make({
769
1037
  version: 1,
770
1038
  repository: metadata.repository,
771
1039
  pullRequestNumber: metadata.number,
@@ -783,7 +1051,7 @@ const executeReview = (binding, options) => Effect.gen(function* () {
783
1051
  const continuity = stateCandidate === void 0 || executionContext?.stateAuthenticator === void 0 ? {
784
1052
  state: void 0,
785
1053
  marker: void 0,
786
- notice: executionContext?.stateAuthenticator?.status === "unavailable" && coverage.status === "complete" ? executionContext.stateAuthenticator.unavailableReason ?? "authenticated continuity state is unavailable" : void 0
1054
+ notice: executionContext?.stateAuthenticator?.status === "unavailable" && inputCoverage.status === "complete" && assurance.status === "settled" ? executionContext.stateAuthenticator.unavailableReason ?? "authenticated continuity state is unavailable" : void 0
787
1055
  } : yield* executionContext.stateAuthenticator.render(stateCandidate).pipe(Effect.match({
788
1056
  onFailure: (error) => ({
789
1057
  state: void 0,
@@ -806,8 +1074,10 @@ const executeReview = (binding, options) => Effect.gen(function* () {
806
1074
  runUrl: options.runUrl,
807
1075
  usage,
808
1076
  usageScope: options.usageScope,
809
- fingerprint: coverage.status === "complete" ? fingerprint : void 0,
1077
+ fingerprint: inputCoverage.status === "complete" && assurance.status === "settled" ? fingerprint : void 0,
810
1078
  coverage,
1079
+ inputCoverage,
1080
+ assurance,
811
1081
  carriedFindings,
812
1082
  carriedConcerns,
813
1083
  reviewMode: executionContext?.mode,
@@ -824,6 +1094,8 @@ const executeReview = (binding, options) => Effect.gen(function* () {
824
1094
  activeFindings,
825
1095
  activeConcerns,
826
1096
  coverage,
1097
+ inputCoverage,
1098
+ assurance,
827
1099
  plan,
828
1100
  turns: result.turns,
829
1101
  usage,
@@ -840,6 +1112,8 @@ const executeReview = (binding, options) => Effect.gen(function* () {
840
1112
  activeFindings,
841
1113
  activeConcerns,
842
1114
  coverage,
1115
+ inputCoverage,
1116
+ assurance,
843
1117
  plan,
844
1118
  published,
845
1119
  turns: result.turns,
@@ -955,12 +1229,12 @@ const make = (options) => {
955
1229
  };
956
1230
  };
957
1231
  /**
958
- * Build the fan-out reviewer: a coordinator that delegates bounded per-unit
959
- * file reviews to attached ephemeral children and merges their findings under
960
- * the same output contract and the same fail-closed publication path as the
961
- * flat reviewer. Child and coordinator execution bounds are packaged and not
962
- * configurable here — the delegation reservation mirrors the child policy,
963
- * and letting the two drift apart is a published-API hazard.
1232
+ * Build the fan-out reviewer: a coordinator schedules host-planned general
1233
+ * and specialist discovery plus independent candidate verification through
1234
+ * attached ephemeral children. Host code reconstructs publication only from
1235
+ * exactly confirmed candidates. Child and coordinator execution bounds are
1236
+ * packaged and not configurable here — the delegation reservation mirrors
1237
+ * the child policy, and letting the two drift apart is a published-API hazard.
964
1238
  */
965
1239
  const makeFanOut = (options) => {
966
1240
  const suite = makeFanOutReviewSuite({
@@ -983,7 +1257,7 @@ const makeFanOut = (options) => {
983
1257
  ...options.modelLabel === void 0 ? [] : [`model=${options.modelLabel}`]
984
1258
  ].join(" ");
985
1259
  const profileSignature = (_mission) => [
986
- "pr-review-profile-v1-fan-out",
1260
+ "pr-review-profile-v3-sharded-request-bound-assurance",
987
1261
  JSON.stringify(guidanceLines),
988
1262
  JSON.stringify(options.ignore ?? []),
989
1263
  `maxFindings=${clampMaxFindings(options.maxFindings)}`,
@@ -1072,7 +1346,7 @@ const settleCallout = (info) => {
1072
1346
  switch (info.conclusion) {
1073
1347
  case "success": return `> ✅ **Code review posted** — verdict \`${info.verdict}\`, ${info.inlineComments} inline comment(s), nothing blocking.`;
1074
1348
  case "blocking": return `> 🛑 **Code review posted** — blocking findings; the check fails until they are addressed.`;
1075
- case "incomplete": return `> ⚠️ **Code review posted** — required coverage is incomplete, so the check fails.`;
1349
+ case "incomplete": return `> ⚠️ **Code review posted** — input coverage or configured review assurance is incomplete, so the check fails.`;
1076
1350
  }
1077
1351
  };
1078
1352
  /** The settled-outcome body written over the in-progress comment. */
@@ -1346,6 +1620,6 @@ const openAiClientLayer = OpenAiClient.layerConfig({ apiKey: Config.redacted(PRO
1346
1620
  /** The Anthropic client Layer, credential from `ANTHROPIC_API_KEY`. */
1347
1621
  const anthropicClientLayer = AnthropicClient.layerConfig({ apiKey: Config.redacted(PROVIDER_CREDENTIAL_ENV.anthropic) }).pipe(Layer.provide(FetchHttpClient.layer));
1348
1622
  //#endregion
1349
- export { AGENT_PROMPT_PREAMBLE as A, ignoringPullRequestSourceLayer as B, PrReview as C, executeReview as D, enforceFindingsBound as E, estimateReviewEffort as F, resolveEffortRung as G, InvalidEffortInput as H, planPublication as I, ReviewShape as J, FailedReviewUnit as K, planWalkthrough as L, ReviewEvent as M, ReviewPublicationPlan as N, fanOutReviewBudgetLimits as O, anchorViolation as P, renderAgentPrompt as R, renderProgressSettleBody as S, buildReviewMission as T, isEffortPosition as U, EFFORT_ALIASES as V, parseEffortPosition as W, collectUnitFileSummaries as X, assessReviewCoverage as Y, 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, ReviewCommentDraft as j, reviewBudgetLimits as k, openAiClientLayer as l, resolveReviewTarget as m, DEFAULT_PROVIDER as n, describeReviewModel as o, readGitHubEvent as p, ReviewCoverage as q, 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 };
1623
+ export { collectUnitFileSummaries as $, AGENT_PROMPT_PREAMBLE as A, EFFORT_ALIASES as B, PrReview as C, executeReview as D, enforceFindingsBound as E, planPublication as F, FailedReviewPass as G, isEffortPosition as H, planWalkthrough as I, ReviewCoverage as J, FailedReviewUnit as K, renderAgentPrompt as L, ReviewEvent as M, ReviewPublicationPlan as N, fanOutReviewBudgetLimits as O, estimateReviewEffort as P, assessReviewPipeline as Q, compileIgnoreGlobs as R, renderProgressSettleBody as S, buildReviewMission as T, parseEffortPosition as U, InvalidEffortInput as V, resolveEffortRung as W, ReviewShape as X, ReviewInputCoverage as Y, assessReviewCoverage as Z, 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, ReviewCommentDraft as j, reviewBudgetLimits as k, openAiClientLayer as l, resolveReviewTarget as m, DEFAULT_PROVIDER as n, describeReviewModel as o, readGitHubEvent as p, ReviewAssurance as q, 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, ignoringPullRequestSourceLayer as z };
1350
1624
 
1351
- //# sourceMappingURL=providers-BE83_Tfo.mjs.map
1625
+ //# sourceMappingURL=providers-BguZK4B_.mjs.map