@effect-agent/pr-review 0.1.0-beta.19 → 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.
- package/README.md +82 -26
- package/dist/action.d.mts +10 -4
- package/dist/action.mjs +17 -27
- package/dist/action.mjs.map +1 -1
- package/dist/cli.mjs +6 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/{fan-out-CQFA-o0v.d.mts → fan-out-D5xrmadQ.d.mts} +288 -223
- package/dist/{github-DnenG3be.mjs → github-DSqZp3Ce.mjs} +622 -195
- package/dist/github-DSqZp3Ce.mjs.map +1 -0
- package/dist/index.d.mts +26 -53
- package/dist/index.mjs +15 -3
- package/dist/index.mjs.map +1 -1
- package/dist/{providers-BE83_Tfo.mjs → providers-CblG1G9b.mjs} +403 -134
- package/dist/providers-CblG1G9b.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 +7 -7
- package/src/action.ts +40 -39
- package/src/cli.ts +4 -0
- package/src/index.ts +1 -0
- package/src/internal/anchors.ts +20 -0
- package/src/internal/coverage.ts +612 -134
- package/src/internal/factory.ts +12 -9
- package/src/internal/fan-out-scripted.ts +61 -94
- package/src/internal/fan-out.ts +505 -256
- 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 +68 -24
- 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 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
|
|
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 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
|
-
|
|
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);
|
|
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
|
-
|
|
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
|
-
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
|
|
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
|
-
|
|
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: [...
|
|
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>
|
|
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:**
|
|
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.
|
|
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("", `⚠️
|
|
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:
|
|
640
|
-
maxOutputTokens:
|
|
641
|
-
maxToolCalls:
|
|
901
|
+
maxInputTokens: 6e5,
|
|
902
|
+
maxOutputTokens: 32e3,
|
|
903
|
+
maxToolCalls: 32,
|
|
642
904
|
maxCostMicrousd: 2e6,
|
|
643
|
-
maxDurationMillis:
|
|
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
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
|
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({
|
|
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" &&
|
|
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:
|
|
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
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
* configurable here — the delegation reservation mirrors
|
|
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-
|
|
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** —
|
|
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,
|
|
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-
|
|
1620
|
+
//# sourceMappingURL=providers-CblG1G9b.mjs.map
|