@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.
- package/README.md +82 -26
- package/dist/action.d.mts +12 -6
- package/dist/action.mjs +17 -27
- package/dist/action.mjs.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/{fan-out-CQFA-o0v.d.mts → fan-out-n-00ppWr.d.mts} +308 -223
- package/dist/{github-DnenG3be.mjs → github-BgtP7Rdv.mjs} +652 -196
- package/dist/github-BgtP7Rdv.mjs.map +1 -0
- package/dist/index.d.mts +27 -54
- package/dist/index.mjs +15 -3
- package/dist/index.mjs.map +1 -1
- package/dist/{providers-BE83_Tfo.mjs → providers-BguZK4B_.mjs} +408 -134
- package/dist/providers-BguZK4B_.mjs.map +1 -0
- package/dist/testing.d.mts +10 -47
- package/dist/testing.mjs +29 -55
- package/dist/testing.mjs.map +1 -1
- package/package.json +2 -2
- package/src/action.ts +40 -39
- package/src/index.ts +1 -0
- package/src/internal/anchors.ts +20 -0
- package/src/internal/coverage.ts +628 -134
- package/src/internal/factory.ts +12 -9
- package/src/internal/fan-out-scripted.ts +61 -94
- package/src/internal/fan-out.ts +560 -255
- package/src/internal/profiles.ts +12 -0
- package/src/internal/progress.ts +1 -1
- package/src/internal/render.ts +51 -25
- package/src/internal/review-agent.ts +74 -25
- package/src/internal/review-state.ts +7 -5
- package/src/internal/review-units.ts +326 -32
- package/src/internal/run.ts +48 -41
- package/dist/github-DnenG3be.mjs.map +0 -1
- package/dist/providers-BE83_Tfo.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
97
|
+
const flatInputCoverage = (files, totalFiles, trace) => {
|
|
55
98
|
const requiredPaths = sortedUnique(files.map((file) => file.path));
|
|
56
|
-
const
|
|
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
|
-
|
|
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
|
|
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 (
|
|
72
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
125
|
+
assignedPaths: sortedUnique(assigned),
|
|
126
|
+
partialPaths: sortedUnique(partial),
|
|
127
|
+
unassignedPaths: sortedUnique(unassigned),
|
|
78
128
|
reasons
|
|
79
129
|
});
|
|
80
130
|
};
|
|
81
|
-
const
|
|
131
|
+
const fanOutInputCoverage = (files, totalFiles) => {
|
|
82
132
|
const plan = planReviewUnits(files, { totalChangedFiles: totalFiles });
|
|
83
|
-
const
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
147
|
+
assignedPaths,
|
|
148
|
+
partialPaths: plan.partialEvidencePaths,
|
|
149
|
+
unassignedPaths,
|
|
131
150
|
reasons
|
|
132
151
|
});
|
|
133
152
|
};
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
|
|
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
|
-
|
|
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: [...
|
|
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>
|
|
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:**
|
|
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.
|
|
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("", `⚠️
|
|
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:
|
|
640
|
-
maxOutputTokens:
|
|
641
|
-
maxToolCalls:
|
|
906
|
+
maxInputTokens: 6e5,
|
|
907
|
+
maxOutputTokens: 32e3,
|
|
908
|
+
maxToolCalls: 32,
|
|
642
909
|
maxCostMicrousd: 2e6,
|
|
643
|
-
maxDurationMillis:
|
|
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
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
|
760
|
-
const
|
|
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" &&
|
|
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:
|
|
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
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
* configurable here — the delegation reservation mirrors
|
|
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-
|
|
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** —
|
|
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,
|
|
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-
|
|
1625
|
+
//# sourceMappingURL=providers-BguZK4B_.mjs.map
|