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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 ReviewMission, Bn as makeReviewInstructions, Bt as reviewCandidateSubjectKey, Dn as ReadFileDiff, En as ReadFile, F as buildProfileMission, Fn as clampMaxFindings, H as toStoredFinding, I as computeProfileFingerprint, In as defaultReviewPolicy, Jn as PullRequestSource, L as fromStoredConcern, Lt as makeFanOutReviewSuite, Mn as ReviewToolkitLayer, O as ReviewState, On as ReviewConcern, Qn as anchorViolation, R as fromStoredFinding, Un as resolveGuidance, V as toStoredConcern, Xn as ReviewInputViolation, Y as renderFingerprintMarker, _n as ListChangedFiles, an as findingAnchorInUnitEvidence, at as FileReviewDelegationFailure, d as gitHubReviewPublisherLayer, dn as FileDiffQuery, dt as FileReviewToolkitLayer, f as gitHubReviewRetirementHostLayer, fn as FileDiffView, jt as fanOutHandlersLayerFor, kn as ReviewFinding, l as gitHubPriorReviewsLayer, lt as FileReviewRequest, n as GitHubApiFailure, o as PublishedReview, on as planReviewUnits, or as isReviewableFile, pt as FileReviewUnitResult, q as computeChangesetFingerprint, qn as PullRequestMetadata, r as GitHubReviewTarget, s as ReviewPublisher, sn as rankAndDedupeFindings, t as DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN, tt as FanOutCoordinatorToolkitLayer, u as gitHubPullRequestSourceLayer, un as CodeReview, w as ReviewExecutionContext, wt as ReviewCandidate } from "./github-DSqZp3Ce.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,342 @@ 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 expectedIds = new Set(candidates.map((candidate) => candidate.candidateId));
325
+ const assessedIds = /* @__PURE__ */ new Set();
326
+ const exactAssessments = result.value.assessments.every((assessment) => {
327
+ if (!expectedIds.has(assessment.candidateId) || assessedIds.has(assessment.candidateId)) return false;
328
+ assessedIds.add(assessment.candidateId);
329
+ return true;
330
+ });
331
+ if (expectedIds.size !== candidates.length || !exactAssessments || assessedIds.size !== expectedIds.size) {
332
+ unsettledCandidates += candidates.length;
333
+ failedPasses.push(FailedReviewPass.make({
334
+ workId,
335
+ stage: "verification",
336
+ errorTag: "VerificationAssessmentMismatch"
337
+ }));
338
+ continue;
339
+ }
340
+ completedVerificationPasses += 1;
341
+ const byId = new Map(candidates.map((candidate) => [candidate.candidateId, candidate]));
342
+ for (const assessment of result.value.assessments) if (assessment.disposition === "confirmed") {
343
+ const candidate = byId.get(assessment.candidateId);
344
+ if (candidate !== void 0) confirmedCandidates.push(candidate);
345
+ } else rejectedCandidates += 1;
346
+ }
347
+ const unexpected = [...trace.declared].filter(([id, declaration]) => declaration.toolName === "delegate_file_review" && !consumedDeclarationIds.has(id));
348
+ for (const [, declaration] of unexpected) {
148
349
  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);
350
+ failedPasses.push(FailedReviewPass.make({
351
+ workId: Option.isSome(request) ? request.value.workId : "invalid-delegation-request",
352
+ stage: Option.isSome(request) && request.value.phase === "verification" ? "verification" : "discovery",
353
+ errorTag: "UnexpectedPass"
354
+ }));
156
355
  }
157
- return entries;
356
+ if (failedPasses.length > 0) reasons.push(boundedListReason("configured review passes did not settle", failedPasses.map((pass) => `${pass.workId} (${pass.errorTag})`)));
357
+ if (unsettledCandidates > 0) reasons.push(`${unsettledCandidates} discovered candidate(s) did not receive exact verification`);
358
+ const requiredSpecialistPasses = plan.discoveryPasses.filter((pass) => pass.perspective === "risk-specialist").length;
359
+ const requiredGeneralDiscoveryPasses = plan.discoveryPasses.length - requiredSpecialistPasses;
360
+ const discoveredCandidates = [...candidatesByUnit.values()].reduce((total, candidates) => total + candidates.length, 0);
361
+ return {
362
+ assurance: ReviewAssurance.make({
363
+ status: reasons.length === 0 ? "settled" : "incomplete",
364
+ requiredGeneralDiscoveryPasses,
365
+ completedGeneralDiscoveryPasses,
366
+ requiredSpecialistPasses,
367
+ completedSpecialistPasses,
368
+ requiredVerificationPasses,
369
+ completedVerificationPasses,
370
+ discoveredCandidates,
371
+ confirmedCandidates: confirmedCandidates.length,
372
+ rejectedCandidates,
373
+ unsettledCandidates,
374
+ failedPasses,
375
+ reasons
376
+ }),
377
+ confirmedFindings: confirmedCandidates.flatMap((candidate) => candidate._tag === "FindingCandidate" ? [candidate.finding] : []),
378
+ confirmedConcerns: confirmedCandidates.flatMap((candidate) => candidate._tag === "ConcernCandidate" ? [candidate.concern] : []),
379
+ walkthrough
380
+ };
158
381
  };
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;
382
+ const compatibilityCoverage = (inputCoverage, assurance) => {
383
+ const assuranceIncomplete = assurance.status === "incomplete";
384
+ const failedUnits = /* @__PURE__ */ new Map();
385
+ for (const pass of assurance.failedPasses) {
386
+ const unitId = pass.workId.slice(0, 8);
387
+ if (!failedUnits.has(unitId)) failedUnits.set(unitId, FailedReviewUnit.make({
388
+ unitId,
389
+ errorTag: `${pass.stage}:${pass.errorTag}`
390
+ }));
391
+ }
164
392
  return ReviewCoverage.make({
165
- ...coverage,
393
+ status: inputCoverage.status === "complete" && !assuranceIncomplete ? "complete" : "incomplete",
394
+ requiredPaths: inputCoverage.requiredPaths,
395
+ reviewedPaths: inputCoverage.assignedPaths,
396
+ unreviewedPaths: sortedUnique([...inputCoverage.partialPaths, ...inputCoverage.unassignedPaths]),
397
+ failedUnits: [...failedUnits.values()].slice(0, 8),
398
+ reasons: [...inputCoverage.reasons, ...assuranceIncomplete ? assurance.reasons : []]
399
+ });
400
+ };
401
+ /** Assess one settled run without trusting coordinator prose or findings. */
402
+ const assessReviewPipeline = (input) => {
403
+ const trace = toolTrace(input.events);
404
+ let inputCoverage = input.shape === "fan-out" ? fanOutInputCoverage(input.files, input.totalFiles) : flatInputCoverage(input.files, input.totalFiles, trace);
405
+ if (input.anchorFiles.length < input.totalAnchorFiles) inputCoverage = ReviewInputCoverage.make({
406
+ ...inputCoverage,
166
407
  status: "incomplete",
167
- reasons: [...coverage.reasons, `full pull-request anchor surface exposed ${input.anchorFiles.length} of ${input.totalAnchorFiles} required files`]
408
+ reasons: [...inputCoverage.reasons, `full pull-request anchor surface exposed ${input.anchorFiles.length} of ${input.totalAnchorFiles} required files`]
409
+ });
410
+ const assessed = input.shape === "fan-out" ? fanOutAssurance(input.files, input.totalFiles, input.anchorFiles, trace) : flatAssurance();
411
+ return {
412
+ inputCoverage,
413
+ assurance: assessed.assurance,
414
+ coverage: compatibilityCoverage(inputCoverage, assessed.assurance),
415
+ confirmedFindings: assessed.confirmedFindings,
416
+ confirmedConcerns: assessed.confirmedConcerns,
417
+ walkthrough: assessed.walkthrough
418
+ };
419
+ };
420
+ /** Compatibility helper; prefer assessReviewPipeline for precise claims. */
421
+ const assessReviewCoverage = (input) => assessReviewPipeline(input).coverage;
422
+ /** Host-verified summaries from successful general discovery passes only. */
423
+ const collectUnitFileSummaries = (events) => {
424
+ const trace = toolTrace(events);
425
+ return delegationDeclarations(trace).flatMap(({ id, request }) => {
426
+ if (request.phase !== "discovery" || request.perspective !== "general") return [];
427
+ const success = trace.succeeded.get(id);
428
+ if (success === void 0 || trace.failed.has(id)) return [];
429
+ const result = Schema.decodeUnknownOption(FileReviewUnitResult)(success.result);
430
+ if (Option.isNone(result) || result.value.phase !== "discovery" || result.value.workId !== request.workId || result.value.unitId !== request.unitId) return [];
431
+ const assigned = new Set(request.paths);
432
+ return result.value.fileSummaries.filter((entry) => assigned.has(entry.path));
168
433
  });
169
434
  };
170
435
  //#endregion
@@ -396,7 +661,8 @@ const severityCounts = (review, carriedFindings = [], carriedConcerns = []) => {
396
661
  */
397
662
  const renderVerdictCallout = (review, options) => {
398
663
  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")}.` : ""}`;
664
+ 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")}.` : ""}`;
665
+ 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
666
  if (counts.blocking > 0) return `> [!CAUTION]\n> ${countNoun(counts.blocking, "blocking finding")} — do not merge before addressing ${counts.blocking === 1 ? "it" : "them"}.`;
401
667
  if (counts.important > 0) return `> [!IMPORTANT]\n> ${countNoun(counts.important, "important finding")} to address before merging.`;
402
668
  if (counts.total > 0) return "> ℹ️ Minor suggestions only — mergeable as-is.";
@@ -492,15 +758,6 @@ const renderReviewMetadata = (options) => [
492
758
  * Why one finding cannot become an inline comment, or undefined when it can.
493
759
  * Exported so tests can pin each rule individually.
494
760
  */
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
761
  /**
505
762
  * Turn one validated review into the exact GitHub publication payload.
506
763
  * `applyVerdict: false` (the safe default) always posts a COMMENT review;
@@ -548,22 +805,27 @@ const planPublication = (review, files, options) => {
548
805
  const parts = [renderVerdictCallout(review, {
549
806
  carriedFindings,
550
807
  carriedConcerns,
551
- coverage: options.coverage
808
+ coverage: options.coverage,
809
+ inputCoverage: options.inputCoverage,
810
+ assurance: options.assurance
552
811
  })];
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}.`);
812
+ 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
813
  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
814
  parts.push("", renderReviewStats(files, options.totalChangedFiles, counts));
815
+ 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
816
  parts.push("", review.summary);
557
817
  if (walkthroughKept && walkthrough.length > 0) parts.push("", renderWalkthrough(walkthrough));
558
818
  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}`));
819
+ if (options.inputCoverage?.status === "incomplete") parts.push("", "### 🛑 Incomplete input coverage", "", ...options.inputCoverage.reasons.map((reason) => `- ${reason}`));
820
+ else if (options.inputCoverage === void 0 && options.coverage?.status === "incomplete") parts.push("", "### 🛑 Incomplete coverage", "", ...options.coverage.reasons.map((reason) => `- ${reason}`));
821
+ if (options.assurance !== void 0 && options.assurance.status !== "settled") parts.push("", "### 🛑 Incomplete review assurance", "", ...options.assurance.reasons.map((reason) => `- ${reason}`));
560
822
  if (carriedFindings.length > 0) parts.push("", "<details>", `<summary>Unresolved findings carried from unchanged scope (${carriedFindings.length})</summary>`, "", ...carriedFindings.map(renderCarriedFinding), "", "</details>");
561
823
  if (carriedConcerns.length > 0) {
562
824
  parts.push("", "### Unresolved concerns carried to the final audit");
563
825
  for (const concern of carriedConcerns) parts.push("", renderConcern(concern));
564
826
  }
565
827
  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.`);
828
+ 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
829
  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
830
  if (consolidatedPromptWanted) parts.push("", promptsKept ? renderConsolidatedAgentPrompt(promptEntries) : "⚠️ Consolidated agent prompt omitted — the body exceeded GitHub's review size cap.");
569
831
  if (omitted > 0) parts.push("", `⚠️ ${countNoun(omitted, "review item")} omitted — the body exceeded GitHub's review size cap.`);
@@ -571,7 +833,7 @@ const planPublication = (review, files, options) => {
571
833
  return parts.join("\n");
572
834
  };
573
835
  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";
836
+ 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
837
  const tail = [
576
838
  renderReviewMetadata({
577
839
  headSha: options.headSha,
@@ -636,11 +898,11 @@ const reviewBudgetLimits = UsageBudgetLimits.make({
636
898
  * while bounded children run.
637
899
  */
638
900
  const fanOutReviewBudgetLimits = UsageBudgetLimits.make({
639
- maxInputTokens: 4e5,
640
- maxOutputTokens: 16e3,
641
- maxToolCalls: 24,
901
+ maxInputTokens: 6e5,
902
+ maxOutputTokens: 32e3,
903
+ maxToolCalls: 32,
642
904
  maxCostMicrousd: 2e6,
643
- maxDurationMillis: 9e5
905
+ maxDurationMillis: 12e5
644
906
  });
645
907
  /** Everything one review run produced, publication receipt included. */
646
908
  var ReviewRunOutcome = class extends Schema.Class("@effect-agent/pr-review/ReviewRunOutcome")({
@@ -651,6 +913,10 @@ var ReviewRunOutcome = class extends Schema.Class("@effect-agent/pr-review/Revie
651
913
  activeConcerns: Schema.Array(ReviewConcern).check(Schema.isMaxLength(10)),
652
914
  /** Host-owned structural coverage used by the Actions check conclusion. */
653
915
  coverage: ReviewCoverage,
916
+ /** Exact path/evidence assignment, distinct from semantic review work. */
917
+ inputCoverage: ReviewInputCoverage,
918
+ /** Settlement of configured discovery, specialist, and verification work. */
919
+ assurance: ReviewAssurance,
654
920
  plan: ReviewPublicationPlan,
655
921
  published: Schema.optionalKey(PublishedReview),
656
922
  turns: Schema.Int.check(Schema.isGreaterThan(0)),
@@ -728,17 +994,22 @@ const executeReview = (binding, options) => Effect.gen(function* () {
728
994
  const result = yield* detached.await;
729
995
  const events = yield* detached.events;
730
996
  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
- })();
997
+ const reviewTotalFiles = executionContext?.totalFiles ?? metadata.totalChangedFiles;
998
+ const pipeline = assessReviewPipeline({
999
+ shape: options.reviewShape ?? "flat",
1000
+ files,
1001
+ totalFiles: reviewTotalFiles,
1002
+ anchorFiles,
1003
+ totalAnchorFiles: metadata.totalChangedFiles,
1004
+ events
1005
+ });
1006
+ const verifiedReview = options.reviewShape !== "fan-out" ? decoded : CodeReview.make({
1007
+ summary: decoded.summary,
1008
+ verdict: decoded.verdict,
1009
+ findings: rankAndDedupeFindings(pipeline.confirmedFindings),
1010
+ ...pipeline.confirmedConcerns.length === 0 ? {} : { concerns: rankAndDedupeConcerns(pipeline.confirmedConcerns) },
1011
+ ...pipeline.walkthrough.length === 0 ? {} : { walkthrough: pipeline.walkthrough }
1012
+ });
742
1013
  const review = enforceFindingsBound(verifiedReview, clampMaxFindings(options.maxFindings));
743
1014
  const usage = yield* budget.snapshot;
744
1015
  const affectedPaths = new Set(executionContext?.affectedPaths ?? files.flatMap((file) => file.previousPath === void 0 ? [file.path] : [file.path, file.previousPath]));
@@ -756,16 +1027,8 @@ const executeReview = (binding, options) => Effect.gen(function* () {
756
1027
  const key = `${concern.title}\u0000${concern.body}`;
757
1028
  return activeConcernKeys.has(key) && !currentConcernKeys.has(key);
758
1029
  });
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({
1030
+ const { assurance, coverage, inputCoverage } = pipeline;
1031
+ 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
1032
  version: 1,
770
1033
  repository: metadata.repository,
771
1034
  pullRequestNumber: metadata.number,
@@ -783,7 +1046,7 @@ const executeReview = (binding, options) => Effect.gen(function* () {
783
1046
  const continuity = stateCandidate === void 0 || executionContext?.stateAuthenticator === void 0 ? {
784
1047
  state: void 0,
785
1048
  marker: void 0,
786
- notice: executionContext?.stateAuthenticator?.status === "unavailable" && coverage.status === "complete" ? executionContext.stateAuthenticator.unavailableReason ?? "authenticated continuity state is unavailable" : void 0
1049
+ notice: executionContext?.stateAuthenticator?.status === "unavailable" && inputCoverage.status === "complete" && assurance.status === "settled" ? executionContext.stateAuthenticator.unavailableReason ?? "authenticated continuity state is unavailable" : void 0
787
1050
  } : yield* executionContext.stateAuthenticator.render(stateCandidate).pipe(Effect.match({
788
1051
  onFailure: (error) => ({
789
1052
  state: void 0,
@@ -806,8 +1069,10 @@ const executeReview = (binding, options) => Effect.gen(function* () {
806
1069
  runUrl: options.runUrl,
807
1070
  usage,
808
1071
  usageScope: options.usageScope,
809
- fingerprint: coverage.status === "complete" ? fingerprint : void 0,
1072
+ fingerprint: inputCoverage.status === "complete" && assurance.status === "settled" ? fingerprint : void 0,
810
1073
  coverage,
1074
+ inputCoverage,
1075
+ assurance,
811
1076
  carriedFindings,
812
1077
  carriedConcerns,
813
1078
  reviewMode: executionContext?.mode,
@@ -824,6 +1089,8 @@ const executeReview = (binding, options) => Effect.gen(function* () {
824
1089
  activeFindings,
825
1090
  activeConcerns,
826
1091
  coverage,
1092
+ inputCoverage,
1093
+ assurance,
827
1094
  plan,
828
1095
  turns: result.turns,
829
1096
  usage,
@@ -840,6 +1107,8 @@ const executeReview = (binding, options) => Effect.gen(function* () {
840
1107
  activeFindings,
841
1108
  activeConcerns,
842
1109
  coverage,
1110
+ inputCoverage,
1111
+ assurance,
843
1112
  plan,
844
1113
  published,
845
1114
  turns: result.turns,
@@ -955,12 +1224,12 @@ const make = (options) => {
955
1224
  };
956
1225
  };
957
1226
  /**
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.
1227
+ * Build the fan-out reviewer: a coordinator schedules host-planned general
1228
+ * and specialist discovery plus independent candidate verification through
1229
+ * attached ephemeral children. Host code reconstructs publication only from
1230
+ * exactly confirmed candidates. Child and coordinator execution bounds are
1231
+ * packaged and not configurable here — the delegation reservation mirrors
1232
+ * the child policy, and letting the two drift apart is a published-API hazard.
964
1233
  */
965
1234
  const makeFanOut = (options) => {
966
1235
  const suite = makeFanOutReviewSuite({
@@ -983,7 +1252,7 @@ const makeFanOut = (options) => {
983
1252
  ...options.modelLabel === void 0 ? [] : [`model=${options.modelLabel}`]
984
1253
  ].join(" ");
985
1254
  const profileSignature = (_mission) => [
986
- "pr-review-profile-v1-fan-out",
1255
+ "pr-review-profile-v3-sharded-request-bound-assurance",
987
1256
  JSON.stringify(guidanceLines),
988
1257
  JSON.stringify(options.ignore ?? []),
989
1258
  `maxFindings=${clampMaxFindings(options.maxFindings)}`,
@@ -1072,7 +1341,7 @@ const settleCallout = (info) => {
1072
1341
  switch (info.conclusion) {
1073
1342
  case "success": return `> ✅ **Code review posted** — verdict \`${info.verdict}\`, ${info.inlineComments} inline comment(s), nothing blocking.`;
1074
1343
  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.`;
1344
+ case "incomplete": return `> ⚠️ **Code review posted** — input coverage or configured review assurance is incomplete, so the check fails.`;
1076
1345
  }
1077
1346
  };
1078
1347
  /** The settled-outcome body written over the in-progress comment. */
@@ -1346,6 +1615,6 @@ const openAiClientLayer = OpenAiClient.layerConfig({ apiKey: Config.redacted(PRO
1346
1615
  /** The Anthropic client Layer, credential from `ANTHROPIC_API_KEY`. */
1347
1616
  const anthropicClientLayer = AnthropicClient.layerConfig({ apiKey: Config.redacted(PROVIDER_CREDENTIAL_ENV.anthropic) }).pipe(Layer.provide(FetchHttpClient.layer));
1348
1617
  //#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 };
1618
+ 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
1619
 
1351
- //# sourceMappingURL=providers-BE83_Tfo.mjs.map
1620
+ //# sourceMappingURL=providers-CblG1G9b.mjs.map