@effect-agent/pr-review 0.1.0-beta.18 → 0.1.0-beta.20
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 +11 -2
- package/dist/action.d.mts +0 -3
- package/dist/action.mjs +49 -31
- package/dist/action.mjs.map +1 -1
- package/dist/cli.mjs +6 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/{github-mmanX6hk.mjs → github-DnenG3be.mjs} +9 -2
- package/dist/github-DnenG3be.mjs.map +1 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +2 -2
- package/dist/{providers-DD2GdrXQ.mjs → providers-BE83_Tfo.mjs} +2 -2
- package/dist/{providers-DD2GdrXQ.mjs.map → providers-BE83_Tfo.mjs.map} +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +7 -7
- package/src/action.ts +68 -24
- package/src/cli.ts +4 -0
- package/src/internal/fingerprint.ts +10 -1
- package/dist/github-mmanX6hk.mjs.map +0 -1
package/dist/testing.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Cn as ChangedFile, Rt as CodeReview, Sn as normalizeRepoRelativePath, _n as MAX_FILE_CHARS, a as PriorReviews, i as PriorReviewLookupFailure, o as PublishedReview, rt as FileReviewReport, s as ReviewPublisher, vn as PullRequestMetadata, xn as ReviewInputViolation, yn as PullRequestSource } from "./github-
|
|
1
|
+
import { Cn as ChangedFile, Rt as CodeReview, Sn as normalizeRepoRelativePath, _n as MAX_FILE_CHARS, a as PriorReviews, i as PriorReviewLookupFailure, o as PublishedReview, rt as FileReviewReport, s as ReviewPublisher, vn as PullRequestMetadata, xn as ReviewInputViolation, yn as PullRequestSource } from "./github-DnenG3be.mjs";
|
|
2
2
|
import { DateTime, Effect, Layer, Option, Ref, Schema, Stream } from "effect";
|
|
3
3
|
import { LanguageModel, Model } from "effect/unstable/ai";
|
|
4
4
|
//#region src/internal/scripted.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-agent/pr-review",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.20",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": {
|
|
6
6
|
"types": "./dist/index.d.mts",
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@effect/ai-anthropic": "4.0.0-
|
|
24
|
-
"@effect/ai-openai": "4.0.0-
|
|
25
|
-
"@effect/platform-node": "4.0.0-
|
|
26
|
-
"effect": "4.0.0-
|
|
27
|
-
"effect-agent": "0.1.0-beta.
|
|
23
|
+
"@effect/ai-anthropic": "4.0.0-rc.110",
|
|
24
|
+
"@effect/ai-openai": "4.0.0-rc.110",
|
|
25
|
+
"@effect/platform-node": "4.0.0-rc.110",
|
|
26
|
+
"effect": "4.0.0-rc.110",
|
|
27
|
+
"effect-agent": "0.1.0-beta.20"
|
|
28
28
|
},
|
|
29
29
|
"description": "Bounded, fail-closed GitHub pull-request reviewer built on the effect-agent public surface: schema-first review contracts, read-only tools, GitHub adapters, a configuration factory, and CLI + GitHub Actions entrypoints.",
|
|
30
30
|
"license": "MIT",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"test": "vp test --passWithNoTests"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@effect/vitest": "4.0.0-
|
|
50
|
+
"@effect/vitest": "4.0.0-rc.110",
|
|
51
51
|
"typescript": "7.0.2",
|
|
52
52
|
"vite-plus": "0.2.6"
|
|
53
53
|
}
|
package/src/action.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
selectReviewRange,
|
|
29
29
|
selectedPullRequestSourceLayer,
|
|
30
30
|
unavailableReviewStateAuthenticatorLayer,
|
|
31
|
+
validateReviewState,
|
|
31
32
|
webCryptoReviewStateAuthenticatorLayer,
|
|
32
33
|
} from "./internal/review-state.ts";
|
|
33
34
|
import { fanOutReviewBudgetLimits, reviewBudgetLimits } from "./internal/run.ts";
|
|
@@ -391,6 +392,39 @@ const concludeReviewState = (state: ReviewState) => {
|
|
|
391
392
|
: ({ conclusion: "success", reasons: [] } as const);
|
|
392
393
|
};
|
|
393
394
|
|
|
395
|
+
const skipCoveredReview = Effect.fn("skipCoveredReview")(function* (input: {
|
|
396
|
+
readonly repository: string;
|
|
397
|
+
readonly pullRequestNumber: number;
|
|
398
|
+
readonly reason: string;
|
|
399
|
+
readonly state: ReviewState;
|
|
400
|
+
readonly fingerprint?: string | undefined;
|
|
401
|
+
}) {
|
|
402
|
+
const result = concludeReviewState(input.state);
|
|
403
|
+
yield* Console.log(
|
|
404
|
+
`Skipping review of ${input.repository}#${input.pullRequestNumber}: ${input.reason}.`,
|
|
405
|
+
);
|
|
406
|
+
yield* writeActionOutputs([
|
|
407
|
+
["skipped", "true"],
|
|
408
|
+
["skip-reason", input.reason],
|
|
409
|
+
...(input.fingerprint === undefined ? [] : ([["fingerprint", input.fingerprint]] as const)),
|
|
410
|
+
["conclusion", result.conclusion],
|
|
411
|
+
["coverage", "complete"],
|
|
412
|
+
["review-mode", "incremental"],
|
|
413
|
+
]);
|
|
414
|
+
yield* writeStepSummary([
|
|
415
|
+
"### Pull-request review skipped",
|
|
416
|
+
`- Reason: ${input.reason}`,
|
|
417
|
+
`- Preserved check conclusion: **${result.conclusion}**`,
|
|
418
|
+
]);
|
|
419
|
+
if (result.conclusion === "blocking") {
|
|
420
|
+
return yield* ReviewGateFailed.make({
|
|
421
|
+
conclusion: "blocking",
|
|
422
|
+
reasons: result.reasons,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
return { _tag: "Skipped", reason: input.reason } satisfies ReviewActionResult;
|
|
426
|
+
});
|
|
427
|
+
|
|
394
428
|
/**
|
|
395
429
|
* Harness one already-built reviewer inside the Actions environment: resolve
|
|
396
430
|
* the target from the event, provide the GitHub source/publisher/prior
|
|
@@ -441,9 +475,15 @@ export const runReviewAction = <E, R, FingerprintE = never, FingerprintR = never
|
|
|
441
475
|
let selection: ReturnType<typeof selectReviewRange> | undefined;
|
|
442
476
|
if (reviewer.profileFingerprint !== undefined) {
|
|
443
477
|
const source = yield* PullRequestSource;
|
|
444
|
-
const [snapshot, profileFingerprint] = yield* Effect.all([
|
|
478
|
+
const [snapshot, profileFingerprint, currentFingerprint] = yield* Effect.all([
|
|
445
479
|
reviewer.snapshot ?? Effect.all({ metadata: source.metadata, files: source.anchorFiles }),
|
|
446
480
|
reviewer.profileFingerprint,
|
|
481
|
+
reviewer.fingerprint === undefined
|
|
482
|
+
? Effect.succeed(undefined)
|
|
483
|
+
: reviewer.fingerprint.pipe(
|
|
484
|
+
Effect.map((fingerprint): string | undefined => fingerprint),
|
|
485
|
+
Effect.orElseSucceed(() => undefined),
|
|
486
|
+
),
|
|
447
487
|
]);
|
|
448
488
|
const { metadata, files: fullFiles } = snapshot;
|
|
449
489
|
const history = options.priorReviews ?? (yield* PriorReviews);
|
|
@@ -465,6 +505,27 @@ export const runReviewAction = <E, R, FingerprintE = never, FingerprintR = never
|
|
|
465
505
|
}),
|
|
466
506
|
}),
|
|
467
507
|
);
|
|
508
|
+
const equivalentPatchState =
|
|
509
|
+
(options.reviewMode ?? "incremental") === "incremental" &&
|
|
510
|
+
options.skipUnchanged !== false &&
|
|
511
|
+
recovered.state !== undefined &&
|
|
512
|
+
currentFingerprint !== undefined &&
|
|
513
|
+
validateReviewState(recovered.state, metadata, profileFingerprint) === undefined &&
|
|
514
|
+
recovered.state.acceptedScopeFingerprint === currentFingerprint
|
|
515
|
+
? recovered.state
|
|
516
|
+
: undefined;
|
|
517
|
+
if (equivalentPatchState !== undefined) {
|
|
518
|
+
return yield* skipCoveredReview({
|
|
519
|
+
repository: target.repository,
|
|
520
|
+
pullRequestNumber: target.number,
|
|
521
|
+
reason:
|
|
522
|
+
equivalentPatchState.reviewedHeadSha === metadata.headSha
|
|
523
|
+
? "the current head already has complete stored review coverage"
|
|
524
|
+
: "the effective pull-request patch is unchanged since the last complete review",
|
|
525
|
+
state: equivalentPatchState,
|
|
526
|
+
fingerprint: currentFingerprint,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
468
529
|
let comparison: ReviewHeadComparison | undefined;
|
|
469
530
|
let baseComparison: ReviewHeadComparison | undefined;
|
|
470
531
|
if (
|
|
@@ -522,30 +583,13 @@ export const runReviewAction = <E, R, FingerprintE = never, FingerprintR = never
|
|
|
522
583
|
selection.files.length === 0 &&
|
|
523
584
|
selection.priorState !== undefined
|
|
524
585
|
) {
|
|
525
|
-
const result = concludeReviewState(selection.priorState);
|
|
526
586
|
const reason = "no changed review scope since the last successfully reviewed head";
|
|
527
|
-
yield*
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
["conclusion", result.conclusion],
|
|
534
|
-
["coverage", "complete"],
|
|
535
|
-
["review-mode", "incremental"],
|
|
536
|
-
]);
|
|
537
|
-
yield* writeStepSummary([
|
|
538
|
-
"### Pull-request review skipped",
|
|
539
|
-
`- Reason: ${reason}`,
|
|
540
|
-
`- Preserved check conclusion: **${result.conclusion}**`,
|
|
541
|
-
]);
|
|
542
|
-
if (result.conclusion === "blocking") {
|
|
543
|
-
return yield* ReviewGateFailed.make({
|
|
544
|
-
conclusion: "blocking",
|
|
545
|
-
reasons: result.reasons,
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
return { _tag: "Skipped", reason } satisfies ReviewActionResult;
|
|
587
|
+
return yield* skipCoveredReview({
|
|
588
|
+
repository: target.repository,
|
|
589
|
+
pullRequestNumber: target.number,
|
|
590
|
+
reason,
|
|
591
|
+
state: selection.priorState,
|
|
592
|
+
});
|
|
549
593
|
}
|
|
550
594
|
} else if (options.skipUnchanged !== false && reviewer.fingerprint !== undefined) {
|
|
551
595
|
// Preserve the pre-state custom harness contract explicitly. The
|
package/src/cli.ts
CHANGED
|
@@ -53,14 +53,17 @@ const effortFlag = Flag.string("effort").pipe(
|
|
|
53
53
|
),
|
|
54
54
|
);
|
|
55
55
|
const postFlag = Flag.boolean("post").pipe(
|
|
56
|
+
Flag.withDefault(false),
|
|
56
57
|
Flag.withDescription("Post the review to GitHub; without it the plan prints to stdout."),
|
|
57
58
|
);
|
|
58
59
|
const applyVerdictFlag = Flag.boolean("apply-verdict").pipe(
|
|
60
|
+
Flag.withDefault(false),
|
|
59
61
|
Flag.withDescription(
|
|
60
62
|
"Map the model verdict onto APPROVE/REQUEST_CHANGES instead of always COMMENT.",
|
|
61
63
|
),
|
|
62
64
|
);
|
|
63
65
|
const fanOutFlag = Flag.boolean("fan-out").pipe(
|
|
66
|
+
Flag.withDefault(false),
|
|
64
67
|
Flag.withDescription(
|
|
65
68
|
"Fan the review out to bounded per-unit subagent reviewers (S1 attached delegation) instead of one flat reviewer.",
|
|
66
69
|
),
|
|
@@ -76,6 +79,7 @@ const maxFindingsFlag = Flag.integer("max-findings").pipe(
|
|
|
76
79
|
Flag.withDescription("Findings bound (1-20); the schema cap of 20 applies regardless."),
|
|
77
80
|
);
|
|
78
81
|
const skipUnchangedFlag = Flag.boolean("skip-unchanged").pipe(
|
|
82
|
+
Flag.withDefault(false),
|
|
79
83
|
Flag.withDescription(
|
|
80
84
|
"Skip when the changeset fingerprint matches the last posted review (explicit runs review unconditionally by default).",
|
|
81
85
|
),
|
|
@@ -49,6 +49,15 @@ const FIELD = "\u0000";
|
|
|
49
49
|
const RECORD = "\u0001";
|
|
50
50
|
const SECTION = "\u0002";
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Unified-diff hunk coordinates describe where a patch applies, not what it
|
|
54
|
+
* changes. A content-equivalent rebase can shift both coordinates while
|
|
55
|
+
* leaving every context/addition/deletion line unchanged, so exclude only
|
|
56
|
+
* those coordinates from the canonical patch representation.
|
|
57
|
+
*/
|
|
58
|
+
const canonicalPatch = (patch: string): string =>
|
|
59
|
+
patch.replace(/^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@/gm, "@@ -_ +_ @@");
|
|
60
|
+
|
|
52
61
|
/**
|
|
53
62
|
* Canonical changeset encoding: sorted by path so provider ordering never
|
|
54
63
|
* matters, with every review-relevant field of every file.
|
|
@@ -57,7 +66,7 @@ const canonicalChangeset = (files: ReadonlyArray<ChangedFile>): string =>
|
|
|
57
66
|
files
|
|
58
67
|
.map(
|
|
59
68
|
(file) =>
|
|
60
|
-
`${file.path}${FIELD}${file.status}${FIELD}${String(file.additions)}${FIELD}${String(file.deletions)}${FIELD}${file.patch
|
|
69
|
+
`${file.path}${FIELD}${file.status}${FIELD}${String(file.additions)}${FIELD}${String(file.deletions)}${FIELD}${file.patch === undefined ? "" : canonicalPatch(file.patch)}${FIELD}${file.reviewBaseContent ?? ""}${FIELD}${file.reviewHeadContent ?? ""}`,
|
|
61
70
|
)
|
|
62
71
|
.sort()
|
|
63
72
|
.join(RECORD);
|