@effect-agent/pr-review 0.1.0-beta.23 → 0.1.0-beta.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -195
- package/dist/action.d.mts +27 -18
- package/dist/action.mjs +60 -39
- package/dist/action.mjs.map +1 -1
- package/dist/cli.mjs +3 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/{fan-out-BJBTAYuh.d.mts → fan-out-CMEsbFLk.d.mts} +455 -177
- package/dist/{github-BbwYzNrC.mjs → github-NjgxGqwM.mjs} +2163 -1518
- package/dist/github-NjgxGqwM.mjs.map +1 -0
- package/dist/index.d.mts +30 -20
- package/dist/index.mjs +3 -3
- package/dist/{providers-NyP-4rS6.mjs → providers-CODZQCmL.mjs} +202 -80
- package/dist/providers-CODZQCmL.mjs.map +1 -0
- package/dist/testing.d.mts +3 -1
- package/dist/testing.mjs +3 -2
- package/dist/testing.mjs.map +1 -1
- package/package.json +2 -2
- package/src/action.ts +141 -78
- package/src/cli.ts +6 -1
- package/src/index.ts +1 -0
- package/src/internal/adjudication.ts +415 -0
- package/src/internal/coverage.ts +41 -60
- package/src/internal/factory.ts +4 -4
- package/src/internal/fan-out.ts +208 -14
- package/src/internal/fingerprint.ts +16 -10
- package/src/internal/fixtures.ts +6 -0
- package/src/internal/github-env.ts +9 -0
- package/src/internal/github.ts +243 -7
- package/src/internal/progress.ts +1 -1
- package/src/internal/render.ts +186 -42
- package/src/internal/retirement.ts +16 -17
- package/src/internal/review-agent.ts +39 -4
- package/src/internal/review-state.ts +315 -105
- package/src/internal/review-units.ts +10 -9
- package/src/internal/run.ts +197 -63
- package/dist/github-BbwYzNrC.mjs.map +0 -1
- package/dist/providers-NyP-4rS6.mjs.map +0 -1
package/src/internal/run.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect,
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
2
|
import {
|
|
3
3
|
makeUsageBudget,
|
|
4
4
|
toRunBudgetHook,
|
|
@@ -9,12 +9,16 @@ import {
|
|
|
9
9
|
} from "effect-agent";
|
|
10
10
|
import { type Tool } from "effect/unstable/ai";
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
collectReviewAdjudications,
|
|
14
|
+
renderAdjudicationContextLine,
|
|
15
|
+
renderPriorFindingContextLine,
|
|
16
|
+
buildPriorReviewContext,
|
|
17
|
+
} from "./adjudication.ts";
|
|
12
18
|
import {
|
|
13
19
|
assessFlatReview,
|
|
14
|
-
compatibilityCoverage,
|
|
15
20
|
fanOutInputCoverage,
|
|
16
21
|
ReviewAssurance,
|
|
17
|
-
ReviewCoverage,
|
|
18
22
|
ReviewInputCoverage,
|
|
19
23
|
} from "./coverage.ts";
|
|
20
24
|
import type { ChangedFile } from "./diff.ts";
|
|
@@ -30,15 +34,21 @@ import {
|
|
|
30
34
|
ReviewMission,
|
|
31
35
|
} from "./review-agent.ts";
|
|
32
36
|
import {
|
|
37
|
+
adjudicationIdentity,
|
|
38
|
+
concernIdentity,
|
|
39
|
+
findingIdentity,
|
|
33
40
|
fromStoredConcern,
|
|
34
41
|
fromStoredFinding,
|
|
42
|
+
MAX_STORED_UNREVIEWED_PASSES,
|
|
35
43
|
MAX_STORED_UNREVIEWED_PATHS,
|
|
36
44
|
ReviewExecutionContext,
|
|
37
45
|
ReviewState,
|
|
46
|
+
StoredAdjudication,
|
|
47
|
+
StoredUnreviewedPass,
|
|
38
48
|
toStoredConcern,
|
|
39
49
|
toStoredFinding,
|
|
40
50
|
} from "./review-state.ts";
|
|
41
|
-
import { rankAndDedupeConcerns, rankAndDedupeFindings } from "./review-units.ts";
|
|
51
|
+
import { rankAndDedupeConcerns, rankAndDedupeFindings, reviewConcernKey } from "./review-units.ts";
|
|
42
52
|
import { PullRequestSource, type PullRequestMetadata } from "./source.ts";
|
|
43
53
|
|
|
44
54
|
// ---------------------------------------------------------------------------
|
|
@@ -89,8 +99,6 @@ export class ReviewRunOutcome extends Schema.Class<ReviewRunOutcome>(
|
|
|
89
99
|
activeFindings: Schema.Array(ReviewFinding).check(Schema.isMaxLength(20)),
|
|
90
100
|
/** All currently unresolved concerns, including concerns carried to final audit. */
|
|
91
101
|
activeConcerns: Schema.Array(ReviewConcern).check(Schema.isMaxLength(10)),
|
|
92
|
-
/** Host-owned structural coverage used by the Actions check conclusion. */
|
|
93
|
-
coverage: ReviewCoverage,
|
|
94
102
|
/** Exact path/evidence assignment, distinct from semantic review work. */
|
|
95
103
|
inputCoverage: ReviewInputCoverage,
|
|
96
104
|
/** Settlement of scheduled discovery, specialist, and verification work. */
|
|
@@ -108,6 +116,8 @@ export class ReviewRunOutcome extends Schema.Class<ReviewRunOutcome>(
|
|
|
108
116
|
reviewMode: Schema.optionalKey(Schema.Literals(["incremental", "full"])),
|
|
109
117
|
reviewReason: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(1_000))),
|
|
110
118
|
state: Schema.optionalKey(ReviewState),
|
|
119
|
+
/** Maintainer adjudications standing against this run's identities. */
|
|
120
|
+
adjudications: Schema.optionalKey(Schema.Array(StoredAdjudication).check(Schema.isMaxLength(20))),
|
|
111
121
|
}) {}
|
|
112
122
|
|
|
113
123
|
export interface ExecuteReviewOptions {
|
|
@@ -135,10 +145,19 @@ export interface ExecuteReviewOptions {
|
|
|
135
145
|
readonly runUrl?: string | undefined;
|
|
136
146
|
}
|
|
137
147
|
|
|
138
|
-
/**
|
|
148
|
+
/**
|
|
149
|
+
* Build the mission one review run frames from the source's snapshot. The
|
|
150
|
+
* optional continuity context (adjudicated identities, prior-round findings
|
|
151
|
+
* on re-reviewed scope) reaches only RUN missions — fingerprint missions stay
|
|
152
|
+
* plain so an adjudication never invalidates skip-unchanged authority.
|
|
153
|
+
*/
|
|
139
154
|
export const buildReviewMission = (
|
|
140
155
|
metadata: PullRequestMetadata,
|
|
141
156
|
files: ReadonlyArray<ChangedFile>,
|
|
157
|
+
context?: {
|
|
158
|
+
readonly adjudicated?: ReadonlyArray<string> | undefined;
|
|
159
|
+
readonly priorFindings?: ReadonlyArray<string> | undefined;
|
|
160
|
+
},
|
|
142
161
|
): ReviewMission =>
|
|
143
162
|
ReviewMission.make({
|
|
144
163
|
repository: metadata.repository,
|
|
@@ -148,6 +167,12 @@ export const buildReviewMission = (
|
|
|
148
167
|
baseRef: metadata.baseRef,
|
|
149
168
|
headRef: metadata.headRef,
|
|
150
169
|
changedFileCount: files.length,
|
|
170
|
+
...(context?.adjudicated !== undefined && context.adjudicated.length > 0
|
|
171
|
+
? { adjudicatedContext: context.adjudicated.slice(0, 20) }
|
|
172
|
+
: {}),
|
|
173
|
+
...(context?.priorFindings !== undefined && context.priorFindings.length > 0
|
|
174
|
+
? { priorFindingContext: context.priorFindings.slice(0, 20) }
|
|
175
|
+
: {}),
|
|
151
176
|
});
|
|
152
177
|
|
|
153
178
|
/** Enforce the configured findings bound on an already-validated review. */
|
|
@@ -165,12 +190,32 @@ export const enforceFindingsBound = (review: CodeReview, maxFindings: number): C
|
|
|
165
190
|
const findingKey = (finding: ReviewFinding): string =>
|
|
166
191
|
`${finding.path}\u0000${finding.startLine}\u0000${finding.endLine}\u0000${finding.severity}\u0000${finding.title}`;
|
|
167
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Continuity inputs resolved BEFORE any model work: the standing maintainer
|
|
195
|
+
* adjudications (fresh host listing merged later-wins over the prior state's
|
|
196
|
+
* stored set) and the prior-round findings whose paths this run re-reviews.
|
|
197
|
+
* The latter are dropped from the carry (the new round re-decides them) but
|
|
198
|
+
* injected as prompt context so successive rounds do not silently contradict
|
|
199
|
+
* each other — context ONLY, never auto-carried into active findings.
|
|
200
|
+
*/
|
|
201
|
+
const resolveReviewContinuityContext = Effect.fn("resolveReviewContinuityContext")(function* () {
|
|
202
|
+
const executionContext = yield* ReviewExecutionContext;
|
|
203
|
+
const priorState =
|
|
204
|
+
executionContext.mode === "incremental" ? executionContext.priorState : undefined;
|
|
205
|
+
const adjudications = yield* collectReviewAdjudications(priorState?.adjudications ?? []);
|
|
206
|
+
const affectedPaths = new Set(executionContext.affectedPaths);
|
|
207
|
+
const priorFindingsOnScope =
|
|
208
|
+
priorState?.unresolvedFindings.filter((finding) => affectedPaths.has(finding.path)) ?? [];
|
|
209
|
+
return { adjudications, priorFindingsOnScope };
|
|
210
|
+
});
|
|
211
|
+
|
|
168
212
|
/** One shape-specific review result, before the shared settlement tail. */
|
|
169
213
|
interface ReviewCore {
|
|
170
214
|
readonly review: CodeReview;
|
|
171
215
|
readonly inputCoverage: ReviewInputCoverage;
|
|
172
216
|
readonly assurance: ReviewAssurance;
|
|
173
217
|
readonly unreviewedPaths: ReadonlyArray<string>;
|
|
218
|
+
readonly unreviewedPasses?: ReadonlyArray<StoredUnreviewedPass> | undefined;
|
|
174
219
|
readonly turns: number;
|
|
175
220
|
}
|
|
176
221
|
|
|
@@ -189,76 +234,137 @@ const settleReviewRun = (
|
|
|
189
234
|
readonly anchorFiles: ReadonlyArray<ChangedFile>;
|
|
190
235
|
readonly fingerprint: string | undefined;
|
|
191
236
|
readonly usage: UsageTotals | undefined;
|
|
237
|
+
readonly adjudications?: ReadonlyArray<StoredAdjudication> | undefined;
|
|
192
238
|
},
|
|
193
239
|
options: ExecuteReviewOptions,
|
|
194
240
|
) =>
|
|
195
241
|
Effect.gen(function* () {
|
|
196
242
|
const { metadata, files, anchorFiles, fingerprint, usage } = context;
|
|
197
|
-
const executionContext =
|
|
198
|
-
|
|
199
|
-
);
|
|
200
|
-
const
|
|
243
|
+
const executionContext = yield* ReviewExecutionContext;
|
|
244
|
+
const adjudications = context.adjudications ?? [];
|
|
245
|
+
const adjudicatedIdentities = new Set(adjudications.map(adjudicationIdentity));
|
|
246
|
+
const isAdjudicatedFinding = (finding: ReviewFinding): boolean =>
|
|
247
|
+
adjudicatedIdentities.has(findingIdentity(finding));
|
|
248
|
+
const isAdjudicatedConcern = (concern: ReviewConcern): boolean =>
|
|
249
|
+
adjudicatedIdentities.has(concernIdentity(concern));
|
|
250
|
+
// Suppress adjudicated model output before any ranking or bounding. A
|
|
251
|
+
// suppressed blocker must never consume the slot of an active finding.
|
|
252
|
+
const filteredReview =
|
|
253
|
+
adjudicatedIdentities.size === 0
|
|
254
|
+
? core.review
|
|
255
|
+
: CodeReview.make({
|
|
256
|
+
summary: core.review.summary,
|
|
257
|
+
verdict: core.review.verdict,
|
|
258
|
+
findings: core.review.findings.filter((finding) => !isAdjudicatedFinding(finding)),
|
|
259
|
+
...(core.review.concerns === undefined
|
|
260
|
+
? {}
|
|
261
|
+
: {
|
|
262
|
+
concerns: core.review.concerns.filter(
|
|
263
|
+
(concern) => !isAdjudicatedConcern(concern),
|
|
264
|
+
),
|
|
265
|
+
}),
|
|
266
|
+
...(core.review.walkthrough === undefined
|
|
267
|
+
? {}
|
|
268
|
+
: { walkthrough: core.review.walkthrough }),
|
|
269
|
+
});
|
|
270
|
+
const reviewPaths = new Set(files.map((file) => file.path));
|
|
271
|
+
const normalizedReview = CodeReview.make({
|
|
272
|
+
...filteredReview,
|
|
273
|
+
...(filteredReview.concerns === undefined
|
|
274
|
+
? {}
|
|
275
|
+
: {
|
|
276
|
+
concerns: filteredReview.concerns.map((concern) => {
|
|
277
|
+
const evidencePaths = concern.evidencePaths;
|
|
278
|
+
if (
|
|
279
|
+
evidencePaths === undefined ||
|
|
280
|
+
evidencePaths.some((path) => !reviewPaths.has(path))
|
|
281
|
+
) {
|
|
282
|
+
const { evidencePaths: _invalid, ...pathless } = concern;
|
|
283
|
+
return ReviewConcern.make(pathless);
|
|
284
|
+
}
|
|
285
|
+
return ReviewConcern.make({
|
|
286
|
+
...concern,
|
|
287
|
+
evidencePaths: [...new Set(evidencePaths)].sort(),
|
|
288
|
+
});
|
|
289
|
+
}),
|
|
290
|
+
}),
|
|
291
|
+
});
|
|
292
|
+
// Adjudicated identities leave the published review entirely: no inline
|
|
293
|
+
// comment, no severity count, no verdict influence — they render only in
|
|
294
|
+
// the plan's collapsed adjudicated section. Only identity-equal items are
|
|
295
|
+
// suppressed; a materially different finding at the same location (a
|
|
296
|
+
// different title) is untouched.
|
|
297
|
+
const review = enforceFindingsBound(normalizedReview, clampMaxFindings(options.maxFindings));
|
|
201
298
|
const { inputCoverage, assurance } = core;
|
|
202
299
|
const unreviewedPaths = [...new Set(core.unreviewedPaths)].sort();
|
|
203
|
-
const reviewTotalFiles = executionContext
|
|
204
|
-
const affectedPaths = new Set(
|
|
205
|
-
executionContext?.affectedPaths ??
|
|
206
|
-
files.flatMap((file) =>
|
|
207
|
-
file.previousPath === undefined ? [file.path] : [file.path, file.previousPath],
|
|
208
|
-
),
|
|
209
|
-
);
|
|
300
|
+
const reviewTotalFiles = executionContext.totalFiles;
|
|
301
|
+
const affectedPaths = new Set(executionContext.affectedPaths);
|
|
210
302
|
const priorState =
|
|
211
|
-
executionContext
|
|
303
|
+
executionContext.mode === "incremental" ? executionContext.priorState : undefined;
|
|
212
304
|
const carriedCandidates =
|
|
213
305
|
priorState?.unresolvedFindings
|
|
214
306
|
.filter((finding) => !affectedPaths.has(finding.path))
|
|
215
307
|
.map(fromStoredFinding) ?? [];
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
clampMaxFindings(options.maxFindings),
|
|
308
|
+
const eligibleCarriedCandidates = carriedCandidates.filter(
|
|
309
|
+
(finding) => !isAdjudicatedFinding(finding),
|
|
219
310
|
);
|
|
311
|
+
const activeFindings = rankAndDedupeFindings([
|
|
312
|
+
...eligibleCarriedCandidates,
|
|
313
|
+
...review.findings.filter((finding) => !isAdjudicatedFinding(finding)),
|
|
314
|
+
]).slice(0, clampMaxFindings(options.maxFindings));
|
|
220
315
|
const activeFindingKeys = new Set(activeFindings.map(findingKey));
|
|
221
316
|
const currentFindingKeys = new Set(review.findings.map(findingKey));
|
|
222
|
-
const carriedFindings =
|
|
317
|
+
const carriedFindings = eligibleCarriedCandidates.filter(
|
|
223
318
|
(finding) =>
|
|
224
319
|
activeFindingKeys.has(findingKey(finding)) && !currentFindingKeys.has(findingKey(finding)),
|
|
225
320
|
);
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
|
|
321
|
+
// A concern remains active only while every host-validated evidence path
|
|
322
|
+
// is unchanged. Touching or removing any one invalidates the old claim;
|
|
323
|
+
// range selection reopens its remaining current paths for fresh context.
|
|
324
|
+
const carriedConcernCandidates =
|
|
325
|
+
priorState?.unresolvedConcerns
|
|
326
|
+
.filter(
|
|
327
|
+
(concern) =>
|
|
328
|
+
concern.evidencePaths !== undefined &&
|
|
329
|
+
concern.evidencePaths.every((path) => !affectedPaths.has(path)),
|
|
330
|
+
)
|
|
331
|
+
.map(fromStoredConcern) ?? [];
|
|
332
|
+
const eligibleCarriedConcernCandidates = carriedConcernCandidates.filter(
|
|
333
|
+
(concern) => !isAdjudicatedConcern(concern),
|
|
334
|
+
);
|
|
229
335
|
const activeConcerns = rankAndDedupeConcerns([
|
|
230
|
-
...
|
|
231
|
-
...(review.concerns ?? []),
|
|
336
|
+
...eligibleCarriedConcernCandidates,
|
|
337
|
+
...(review.concerns ?? []).filter((concern) => !isAdjudicatedConcern(concern)),
|
|
232
338
|
]);
|
|
233
|
-
const currentConcernKeys = new Set(
|
|
234
|
-
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
activeConcerns.map((concern) => `${concern.title}\u0000${concern.body}`),
|
|
238
|
-
);
|
|
239
|
-
const carriedConcerns = carriedConcernCandidates.filter((concern) => {
|
|
240
|
-
const key = `${concern.title}\u0000${concern.body}`;
|
|
339
|
+
const currentConcernKeys = new Set((review.concerns ?? []).map(reviewConcernKey));
|
|
340
|
+
const activeConcernKeys = new Set(activeConcerns.map(reviewConcernKey));
|
|
341
|
+
const carriedConcerns = eligibleCarriedConcernCandidates.filter((concern) => {
|
|
342
|
+
const key = reviewConcernKey(concern);
|
|
241
343
|
return activeConcernKeys.has(key) && !currentConcernKeys.has(key);
|
|
242
344
|
});
|
|
243
345
|
const settled =
|
|
244
346
|
inputCoverage.status === "complete" &&
|
|
245
347
|
assurance.status !== "incomplete" &&
|
|
246
348
|
unreviewedPaths.length === 0;
|
|
349
|
+
const concernsHaveEvidencePaths = activeConcerns.every(
|
|
350
|
+
(concern) => concern.evidencePaths !== undefined,
|
|
351
|
+
);
|
|
247
352
|
// The fingerprint marker is standalone skip authority for fingerprint-only
|
|
248
353
|
// harnesses, so it is embedded only for a fully settled run.
|
|
249
|
-
const skipFingerprint = settled ? fingerprint : undefined;
|
|
354
|
+
const skipFingerprint = settled && concernsHaveEvidencePaths ? fingerprint : undefined;
|
|
250
355
|
const carriedScopeFits = unreviewedPaths.length <= MAX_STORED_UNREVIEWED_PATHS;
|
|
251
356
|
const stateCandidate =
|
|
252
|
-
executionContext !== undefined &&
|
|
253
357
|
fingerprint !== undefined &&
|
|
358
|
+
executionContext.profileFingerprint !== undefined &&
|
|
254
359
|
metadata.baseSha !== undefined &&
|
|
255
360
|
// The fingerprint and stored baseline describe the FULL pull-request
|
|
256
361
|
// surface; a truncated anchor surface cannot make either claim.
|
|
257
362
|
anchorFiles.length >= metadata.totalChangedFiles &&
|
|
258
363
|
carriedScopeFits &&
|
|
364
|
+
concernsHaveEvidencePaths &&
|
|
259
365
|
executionContext.stateAuthenticator?.status === "available"
|
|
260
366
|
? ReviewState.make({
|
|
261
|
-
version:
|
|
367
|
+
version: 1,
|
|
262
368
|
repository: metadata.repository,
|
|
263
369
|
pullRequestNumber: metadata.number,
|
|
264
370
|
baseRef: metadata.baseRef,
|
|
@@ -266,24 +372,27 @@ const settleReviewRun = (
|
|
|
266
372
|
headRef: metadata.headRef,
|
|
267
373
|
reviewedHeadSha: metadata.headSha,
|
|
268
374
|
profileFingerprint: executionContext.profileFingerprint,
|
|
269
|
-
|
|
375
|
+
settledScopeFingerprint: fingerprint,
|
|
270
376
|
reviewedPathCount: anchorFiles.length,
|
|
271
377
|
unresolvedFindings: activeFindings.map(toStoredFinding),
|
|
272
378
|
unresolvedConcerns: activeConcerns.map(toStoredConcern),
|
|
273
379
|
unreviewedPaths,
|
|
380
|
+
unreviewedPasses: (core.unreviewedPasses ?? []).slice(0, MAX_STORED_UNREVIEWED_PASSES),
|
|
274
381
|
settled,
|
|
275
382
|
lastReviewMode: executionContext.mode,
|
|
383
|
+
...(adjudications.length === 0 ? {} : { adjudications }),
|
|
276
384
|
})
|
|
277
385
|
: undefined;
|
|
278
386
|
const continuity =
|
|
279
|
-
stateCandidate === undefined || executionContext
|
|
387
|
+
stateCandidate === undefined || executionContext.stateAuthenticator === undefined
|
|
280
388
|
? {
|
|
281
389
|
state: undefined,
|
|
282
390
|
marker: undefined,
|
|
283
|
-
notice:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
391
|
+
notice: !carriedScopeFits
|
|
392
|
+
? `carried unreviewed scope (${unreviewedPaths.length} paths) exceeded the ${MAX_STORED_UNREVIEWED_PATHS}-path continuity bound`
|
|
393
|
+
: !concernsHaveEvidencePaths
|
|
394
|
+
? "one or more review concerns lacked host-validated affected paths"
|
|
395
|
+
: executionContext.stateAuthenticator?.status === "unavailable"
|
|
287
396
|
? (executionContext.stateAuthenticator.unavailableReason ??
|
|
288
397
|
"authenticated continuity state is unavailable")
|
|
289
398
|
: undefined,
|
|
@@ -316,29 +425,29 @@ const settleReviewRun = (
|
|
|
316
425
|
unreviewedPaths,
|
|
317
426
|
carriedFindings,
|
|
318
427
|
carriedConcerns,
|
|
319
|
-
reviewMode: executionContext
|
|
320
|
-
reviewReason: executionContext
|
|
321
|
-
baselineSha: executionContext
|
|
428
|
+
reviewMode: executionContext.mode,
|
|
429
|
+
reviewReason: executionContext.reason,
|
|
430
|
+
baselineSha: executionContext.baselineSha,
|
|
322
431
|
reviewFilesVisible: files.length,
|
|
323
432
|
reviewTotalFiles,
|
|
324
433
|
stateMarker: continuity.marker,
|
|
325
434
|
stateNotice: continuity.notice,
|
|
435
|
+
...(adjudications.length === 0 ? {} : { adjudications }),
|
|
326
436
|
});
|
|
327
437
|
const shared = {
|
|
328
438
|
review,
|
|
329
439
|
activeFindings,
|
|
330
440
|
activeConcerns,
|
|
331
|
-
coverage: compatibilityCoverage(inputCoverage, assurance),
|
|
332
441
|
inputCoverage,
|
|
333
442
|
assurance,
|
|
334
443
|
unreviewedPaths,
|
|
335
444
|
plan,
|
|
336
445
|
turns: core.turns,
|
|
337
446
|
...(usage === undefined ? {} : { usage }),
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
: { reviewMode: executionContext.mode, reviewReason: executionContext.reason }),
|
|
447
|
+
reviewMode: executionContext.mode,
|
|
448
|
+
reviewReason: executionContext.reason,
|
|
341
449
|
...(continuity.state === undefined ? {} : { state: continuity.state }),
|
|
450
|
+
...(adjudications.length === 0 ? {} : { adjudications }),
|
|
342
451
|
};
|
|
343
452
|
if (!options.post) return ReviewRunOutcome.make(shared);
|
|
344
453
|
const publisher = yield* ReviewPublisher;
|
|
@@ -375,10 +484,12 @@ export const executeReview = <
|
|
|
375
484
|
const metadata = yield* source.metadata;
|
|
376
485
|
const files = yield* source.changedFiles;
|
|
377
486
|
const anchorFiles = yield* source.anchorFiles;
|
|
378
|
-
const executionContext =
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
487
|
+
const executionContext = yield* ReviewExecutionContext;
|
|
488
|
+
const continuity = yield* resolveReviewContinuityContext();
|
|
489
|
+
const mission = buildReviewMission(metadata, files, {
|
|
490
|
+
adjudicated: continuity.adjudications.map(renderAdjudicationContextLine),
|
|
491
|
+
priorFindings: continuity.priorFindingsOnScope.map(renderPriorFindingContextLine),
|
|
492
|
+
});
|
|
382
493
|
const fullMission = buildReviewMission(metadata, anchorFiles);
|
|
383
494
|
const fingerprint =
|
|
384
495
|
options.signature === undefined
|
|
@@ -398,7 +509,7 @@ export const executeReview = <
|
|
|
398
509
|
const review = yield* Schema.decodeUnknownEffect(CodeReview)(result.output);
|
|
399
510
|
const assessment = assessFlatReview({
|
|
400
511
|
files,
|
|
401
|
-
totalFiles: executionContext
|
|
512
|
+
totalFiles: executionContext.totalFiles,
|
|
402
513
|
anchorFiles,
|
|
403
514
|
totalAnchorFiles: metadata.totalChangedFiles,
|
|
404
515
|
events,
|
|
@@ -412,7 +523,7 @@ export const executeReview = <
|
|
|
412
523
|
unreviewedPaths: assessment.unreviewedPaths,
|
|
413
524
|
turns: result.turns,
|
|
414
525
|
},
|
|
415
|
-
{ metadata, files, anchorFiles, fingerprint, usage },
|
|
526
|
+
{ metadata, files, anchorFiles, fingerprint, usage, adjudications: continuity.adjudications },
|
|
416
527
|
options,
|
|
417
528
|
);
|
|
418
529
|
});
|
|
@@ -432,9 +543,7 @@ export const executeFanOutReview = <Provider, ModelProvides, ModelRequires>(
|
|
|
432
543
|
const metadata = yield* source.metadata;
|
|
433
544
|
const files = yield* source.changedFiles;
|
|
434
545
|
const anchorFiles = yield* source.anchorFiles;
|
|
435
|
-
const executionContext =
|
|
436
|
-
yield* Effect.serviceOption(ReviewExecutionContext),
|
|
437
|
-
);
|
|
546
|
+
const executionContext = yield* ReviewExecutionContext;
|
|
438
547
|
const fullMission = buildReviewMission(metadata, anchorFiles);
|
|
439
548
|
const fingerprint =
|
|
440
549
|
options.signature === undefined
|
|
@@ -442,13 +551,30 @@ export const executeFanOutReview = <Provider, ModelProvides, ModelRequires>(
|
|
|
442
551
|
: yield* computeChangesetFingerprint(anchorFiles, options.signature(fullMission));
|
|
443
552
|
|
|
444
553
|
const budget = yield* makeUsageBudget(options.limits ?? fanOutReviewBudgetLimits);
|
|
445
|
-
const totalFiles = executionContext
|
|
554
|
+
const totalFiles = executionContext.totalFiles;
|
|
555
|
+
const continuity = yield* resolveReviewContinuityContext();
|
|
446
556
|
const pipeline = yield* runFanOutReview(binding, {
|
|
447
557
|
files,
|
|
448
558
|
anchorFiles,
|
|
449
559
|
totalChangedFiles: totalFiles,
|
|
450
560
|
maxFindings: options.maxFindings,
|
|
451
561
|
budget: toRunBudgetHook(budget),
|
|
562
|
+
...(executionContext.retryPaths.length > 0
|
|
563
|
+
? {
|
|
564
|
+
retry: {
|
|
565
|
+
paths: executionContext.retryPaths,
|
|
566
|
+
stages: executionContext.retryStages,
|
|
567
|
+
},
|
|
568
|
+
}
|
|
569
|
+
: {}),
|
|
570
|
+
...(continuity.adjudications.length > 0 || continuity.priorFindingsOnScope.length > 0
|
|
571
|
+
? {
|
|
572
|
+
priorContext: buildPriorReviewContext(
|
|
573
|
+
continuity.adjudications,
|
|
574
|
+
continuity.priorFindingsOnScope,
|
|
575
|
+
),
|
|
576
|
+
}
|
|
577
|
+
: {}),
|
|
452
578
|
});
|
|
453
579
|
const inputCoverage = fanOutInputCoverage({
|
|
454
580
|
plan: pipeline.plan,
|
|
@@ -464,9 +590,17 @@ export const executeFanOutReview = <Provider, ModelProvides, ModelRequires>(
|
|
|
464
590
|
inputCoverage,
|
|
465
591
|
assurance: pipeline.assurance,
|
|
466
592
|
unreviewedPaths: pipeline.unreviewedPaths,
|
|
593
|
+
unreviewedPasses: pipeline.unreviewedPasses
|
|
594
|
+
.slice(0, MAX_STORED_UNREVIEWED_PASSES)
|
|
595
|
+
.map((pass) =>
|
|
596
|
+
StoredUnreviewedPass.make({
|
|
597
|
+
stage: pass.stage,
|
|
598
|
+
paths: pass.paths.slice(0, 12),
|
|
599
|
+
}),
|
|
600
|
+
),
|
|
467
601
|
turns: pipeline.turns,
|
|
468
602
|
},
|
|
469
|
-
{ metadata, files, anchorFiles, fingerprint, usage },
|
|
603
|
+
{ metadata, files, anchorFiles, fingerprint, usage, adjudications: continuity.adjudications },
|
|
470
604
|
options,
|
|
471
605
|
);
|
|
472
606
|
});
|