@effect-agent/pr-review 0.1.0-beta.28 → 0.1.0-beta.29

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.
Files changed (52) hide show
  1. package/README.md +9 -204
  2. package/dist/index.d.mts +87 -914
  3. package/dist/index.mjs +163 -71
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +3 -18
  6. package/src/index.ts +1 -25
  7. package/src/review.ts +212 -0
  8. package/dist/action.d.mts +0 -215
  9. package/dist/action.mjs +0 -505
  10. package/dist/action.mjs.map +0 -1
  11. package/dist/cli.d.mts +0 -1
  12. package/dist/cli.mjs +0 -106
  13. package/dist/cli.mjs.map +0 -1
  14. package/dist/fan-out-C3yG1cx3.d.mts +0 -1526
  15. package/dist/github-CCuLgyqb.mjs +0 -3437
  16. package/dist/github-CCuLgyqb.mjs.map +0 -1
  17. package/dist/logging-Q4j0oub-.mjs +0 -75
  18. package/dist/logging-Q4j0oub-.mjs.map +0 -1
  19. package/dist/providers-Br9FRn7j.mjs +0 -1349
  20. package/dist/providers-Br9FRn7j.mjs.map +0 -1
  21. package/dist/testing.d.mts +0 -86
  22. package/dist/testing.mjs +0 -184
  23. package/dist/testing.mjs.map +0 -1
  24. package/src/action.ts +0 -906
  25. package/src/cli.ts +0 -235
  26. package/src/internal/action-entry.ts +0 -45
  27. package/src/internal/adjudication.ts +0 -415
  28. package/src/internal/anchors.ts +0 -20
  29. package/src/internal/coverage.ts +0 -357
  30. package/src/internal/diff.ts +0 -193
  31. package/src/internal/effort.ts +0 -86
  32. package/src/internal/factory.ts +0 -357
  33. package/src/internal/fan-out-scripted.ts +0 -77
  34. package/src/internal/fan-out.ts +0 -1148
  35. package/src/internal/fingerprint.ts +0 -89
  36. package/src/internal/fixtures.ts +0 -148
  37. package/src/internal/github-env.ts +0 -164
  38. package/src/internal/github.ts +0 -1218
  39. package/src/internal/ignore.ts +0 -88
  40. package/src/internal/logging.ts +0 -124
  41. package/src/internal/profiles.ts +0 -91
  42. package/src/internal/progress.ts +0 -433
  43. package/src/internal/providers.ts +0 -133
  44. package/src/internal/render.ts +0 -819
  45. package/src/internal/retirement.ts +0 -337
  46. package/src/internal/review-agent.ts +0 -543
  47. package/src/internal/review-state.ts +0 -782
  48. package/src/internal/review-units.ts +0 -493
  49. package/src/internal/run.ts +0 -611
  50. package/src/internal/scripted.ts +0 -108
  51. package/src/internal/source.ts +0 -110
  52. package/src/testing.ts +0 -8
package/dist/index.mjs CHANGED
@@ -1,79 +1,171 @@
1
- import { $ as ReviewDiscoveryPass, $n as ReadFileDiff, $t as MAX_STORED_ADJUDICATIONS, A as MAX_CHILD_FINDINGS, An as toStoredFinding, Ar as hasReviewableContent, At as buildPriorReviewContext, B as assessmentSettlesSuggestionExactly, Bn as FileSliceQuery, Bt as threadFindingTarget, C as FileReviewBrief, Cn as fromStoredFinding, Cr as normalizeRepoRelativePath, Ct as anchorViolation, D as FileReviewer, Dn as selectReviewRange, Dr as MAX_REVIEW_CONTENT_CHARS, Dt as MAX_THREAD_ADJUDICATION_COMMANDS, E as FileReviewToolkit, En as isLineageAncestor, Er as ChangedPath, Et as AdjudicationComment, F as ReviewCandidate, Fn as ChangedFilesView, Ft as noReviewAdjudicationHostLayer, G as makeFileReviewerInstructions, Gn as MAX_CONCERNS, Gt as ReviewRetirementHost, H as defaultFileReviewerPolicy, Hn as FindingSeverity, Ht as RetirableReview, I as ReviewCandidateId, In as CodeReview, It as parseIssueAdjudication, J as MAX_MERGED_FINDINGS, Jn as MAX_WALKTHROUGH_ENTRIES, Jt as hasReviewMetadataMarker, K as reviewCandidateSubjectKey, Kn as MAX_FINDINGS, Kt as ReviewRetirementReport, L as ReviewPassMisbehaved, Ln as FileDiffQuery, Lt as parseThreadAdjudication, M as MAX_REVIEW_CHILDREN, Mn as validateReviewState, Mr as parsePatch, Mt as deriveAdjudications, N as MAX_UNIT_CANDIDATES, Nn as webCryptoReviewStateAuthenticatorLayer, Nr as renderReviewContent, Nt as mergeAdjudications, O as FindingCandidate, On as selectedPullRequestSourceLayer, Or as annotatePatch, Ot as ReviewAdjudicationFailure, P as REVIEW_UNIT_CONCURRENCY, Pn as ChangedFileSummary, Pt as noReviewAdjudicationHost, Q as MAX_UNIT_FILES, Qn as ReadFile, Qt as MAX_REVIEW_STATE_MARKER_CHARS, R as ReviewWorkPerspective, Rn as FileDiffView, Rt as renderAdjudicationContextLine, S as DiscoveredConcern, Sn as fromStoredConcern, Sr as ReviewInputViolation, St as splitCarriedScope, T as FileReviewReport, Tn as fullReviewSelection, Tr as ChangedFileStatus, Tt as AdjudicableThread, U as fileReviewerInstructions, Un as ListChangedFiles, Ut as RetirableReviewComment, V as confirmedFindingForPublication, Vn as FindingCategory, Vt as INLINE_FINDING_TITLE_PATTERN, W as makeFileReviewerDefinition, Wn as ListChangedFilesQuery, Wt as ReviewRetirementFailure, X as MAX_REVIEW_UNITS, Xn as PullRequestReviewer, Xt as AdjudicationDisposition, Y as MAX_REPORTED_UNASSIGNED_EVIDENCE_SHARDS, Yn as MAX_WALKTHROUGH_SUMMARY_CHARS, Yt as retireStaleReviews, Z as MAX_UNIT_EVIDENCE_SHARDS, Zn as REVIEW_TOOL_RESULT_MAX_BYTES, Zt as GitCommitSha, _ as computeProfileFingerprint, _n as UnreviewedStage, _r as MAX_CHANGED_FILES, _t as ReviewInputCoverage, a as PriorReviews, an as ReviewMode, ar as ReviewVerdict, at as ReviewUnit, b as CandidateAssessment, bn as concernIdentity, br as PullRequestSource, bt as fanOutInputCoverage, c as fingerprintUnchanged, cn as ReviewStateAuthenticationFailure, cr as defaultReviewPolicy, ct as UNIT_EVIDENCE_CHAR_BUDGET, d as gitHubReviewAdjudicationHostLayer, dn as ReviewStateMarkerTooLarge, dr as listChangedFilesHandler, dt as planReviewUnits, en as MAX_STORED_UNREVIEWED_PASSES, er as ReviewConcern, et as ReviewDiscoveryPerspective, f as gitHubReviewPublisherLayer, fn as ReviewTreeComparison, fr as makeReviewInstructions, ft as rankAndDedupeConcerns, g as computeChangesetFingerprint, gn as StoredUnreviewedPass, gr as reviewInstructions, gt as ReviewAssurance, h as FINGERPRINT_MARKER_LENGTH, hn as StoredReviewFinding, hr as resolveGuidance, ht as FailedReviewPass, i as PriorReviewLookupFailure, in as ReviewHeadComparison, ir as ReviewToolkitLayer, it as ReviewRiskCategory, j as MAX_FILE_REVIEW_TOOL_CALLS, jn as unavailableReviewStateAuthenticatorLayer, jr as isReviewableFile, jt as collectReviewAdjudications, k as MAX_CHILD_CONCERNS, kn as toStoredConcern, kr as commentableLines, kt as ReviewAdjudicationHost, l as gitHubPriorReviewsLayer, ln as ReviewStateAuthenticator, lr as fileDiffView, lt as classifyReviewRisks, m as parseGitHubSubmittedAt, mn as StoredReviewConcern, mr as readFileHandler, mt as reviewConcernKey, n as GitHubApiFailure, nn as MAX_TREE_COMPARISON_PATHS, nr as ReviewMission, nt as ReviewEvidenceShardId, o as PublishedReview, on as ReviewScopeMode, or as WalkthroughEntry, ot as ReviewUnitId, p as gitHubReviewRetirementHostLayer, pn as StoredAdjudication, pr as readFileDiffHandler, pt as rankAndDedupeFindings, q as runFanOutReview, qn as MAX_PATCH_CHARS, qt as decideReviewRetirement, r as GitHubReviewTarget, rn as ReviewExecutionContext, rr as ReviewToolkit, rt as ReviewPassId, s as ReviewPublisher, sn as ReviewState, sr as clampMaxFindings, st as ReviewUnitPlan, t as DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN, tn as MAX_STORED_UNREVIEWED_PATHS, tr as ReviewFinding, tt as ReviewEvidenceShard, u as gitHubPullRequestSourceLayer, un as ReviewStateMarker, ur as fileReviewEvidenceChunks, ut as findingAnchorInUnitEvidence, v as extractFingerprint, vn as adjudicationIdentity, vr as MAX_FILE_CHARS, vt as assessFlatReview, w as FileReviewEvidence, wn as fullReviewExecutionContextLayer, wr as ChangedFile, wt as AUTHORIZED_ADJUDICATION_ASSOCIATIONS, x as ConcernCandidate, xn as findingIdentity, xr as PullRequestSourceFailure, xt as flatAssurance, y as renderFingerprintMarker, yn as buildProfileMission, yr as PullRequestMetadata, yt as boundedListReason, z as ReviewWorkPhase, zn as FileSlice, zt as renderPriorFindingContextLine } from "./github-CCuLgyqb.mjs";
2
- import { A as enforceFindingsBound, B as planPublication, C as parseProgressClaim, D as PrReview, E as renderProgressSettleBody, F as AGENT_PROMPT_PREAMBLE, G as EFFORT_ALIASES, H as renderAgentPrompt, I as ReviewCommentDraft, J as parseEffortPosition, K as InvalidEffortInput, L as ReviewEvent, M as executeReview, N as fanOutReviewBudgetLimits, O as ReviewRunOutcome, P as reviewBudgetLimits, R as ReviewPublicationPlan, S as noopReviewProgressReporterLayer, T as renderProgressClaimMarker, U as compileIgnoreGlobs, V as planWalkthrough, W as ignoringPullRequestSourceLayer, Y as resolveEffortRung, _ as readGitHubEvent, a as PROVIDER_EFFORT_RUNGS, b as ReviewProgressReporter, c as anthropicClientLayer, d as makeOpenAiReviewModel, f as openAiClientLayer, g as gitHubReviewLayers, h as ReviewTargetUnresolved, i as PROVIDER_CREDENTIAL_ENV, j as executeFanOutReview, k as buildReviewMission, l as describeReviewModel, m as GitHubEventWire, n as DEFAULT_PROVIDER, o as ReviewProvider, p as validateReviewServiceTier, q as isEffortPosition, r as OpenAiServiceTier, s as UnsupportedServiceTierProvider, t as DEFAULT_MODEL, u as makeAnthropicReviewModel, v as resolveReviewTarget, w as renderProgressBeginBody, x as gitHubReviewProgressLayer, y as PROGRESS_COMMENT_MARKER_PREFIX, z as estimateReviewEffort } from "./providers-Br9FRn7j.mjs";
3
- import { n as compactReviewLoggingLayer, r as formatCompactLogLine, t as compactReviewLogger } from "./logging-Q4j0oub-.mjs";
4
- import { Schema } from "effect";
5
- //#region src/internal/profiles.ts
6
- /** The flat reviewer's committed capability claim. */
7
- var PullRequestReviewerProfile = class extends Schema.Class("@effect-agent/pr-review/PullRequestReviewerProfile")({
8
- /** Ephemeral runtime: one bounded AgentRuntime.run per invocation. */
9
- deploymentClass: Schema.Literal("E"),
10
- /** Every model-callable tool is a read of the pull request; none mutate. */
11
- readOnlyToolSurface: Schema.Literal(true),
12
- /** The review is posted by the host AFTER the run settles, never by a tool. */
13
- publicationOutsideAgentLoop: Schema.Literal(true),
14
- /** Finding anchors are validated against the parsed diff before posting. */
15
- anchorsValidatedBeforePublication: Schema.Literal(true),
16
- /** The live profile is env-gated out of every ordinary test gate. */
17
- liveProfileOptIn: Schema.Literal(true),
18
- /** Never claimed at any phase (DUR-003). */
19
- exactlyOnceExternalEffects: Schema.Literal(false)
1
+ import { Effect, Schema } from "effect";
2
+ import { Agent, AgentPolicy, AgentRuntime, IdGenerator, UsageBudgetLimits, makeUsageBudget, toRunBudgetHook } from "effect-agent";
3
+ import { Toolkit } from "effect/unstable/ai";
4
+ //#region src/review.ts
5
+ const ReviewPath = Schema.NonEmptyString.check(Schema.isMaxLength(512));
6
+ const Revision = Schema.NonEmptyString.check(Schema.isMaxLength(128));
7
+ /** One complete textual patch supplied by the host. */
8
+ var ReviewChange = class extends Schema.Class("@effect-agent/pr-review/ReviewChange")({
9
+ path: ReviewPath,
10
+ patch: Schema.NonEmptyString.check(Schema.isMaxLength(8e4))
20
11
  }) {};
21
- const pullRequestReviewerProfile = PullRequestReviewerProfile.make({
22
- deploymentClass: "E",
23
- readOnlyToolSurface: true,
24
- publicationOutsideAgentLoop: true,
25
- anchorsValidatedBeforePublication: true,
26
- liveProfileOptIn: true,
27
- exactlyOnceExternalEffects: false
28
- });
29
- /** The fan-out reviewer's committed capability claim, schema-first. */
30
- var FanOutReviewerProfile = class extends Schema.Class("@effect-agent/pr-review/FanOutReviewerProfile")({
31
- /** Ephemeral runtime: bounded host-scheduled child runs per invocation. */
32
- deploymentClass: Schema.Literal("E"),
33
- /** Every model-callable surface is evidence-only; children expose no tools. */
34
- readOnlyToolSurface: Schema.Literal(true),
35
- /** The review is posted by the host AFTER the run settles, never by a tool. */
36
- publicationOutsideAgentLoop: Schema.Literal(true),
37
- /** Child findings are untrusted; anchors are validated against the parsed diff. */
38
- anchorsValidatedBeforePublication: Schema.Literal(true),
39
- /** Host code schedules every pass from the deterministic plan; no coordinator model. */
40
- hostScheduledPasses: Schema.Literal(true),
41
- /** A failed pass is retried once, then reported and carried as unreviewed scope. */
42
- failedPassesRetriedOnceThenCarried: Schema.Literal(true),
43
- /** Risk categories and required specialist passes are pure host policy. */
44
- hostOwnedRiskClassification: Schema.Literal(true),
45
- /** Every host-classified high-risk unit receives a fresh specialist pass. */
46
- redundantHighRiskDiscovery: Schema.Literal(true),
47
- /** Only exact candidates confirmed by a fresh verifier child may publish. */
48
- independentCandidateVerification: Schema.Literal(true),
49
- /** No bounded model pipeline proves that a pull request is defect-free. */
50
- defectAbsenceProven: Schema.Literal(false),
51
- /** The live profile is env-gated out of every ordinary test gate. */
52
- liveProfileOptIn: Schema.Literal(true),
53
- /** Never claimed at any phase (DUR-003). */
54
- exactlyOnceExternalEffects: Schema.Literal(false)
12
+ /** The provider-neutral input to one review pass. */
13
+ var ReviewRequest = class extends Schema.Class("@effect-agent/pr-review/ReviewRequest")({
14
+ title: Schema.String.check(Schema.isMaxLength(1e3)),
15
+ description: Schema.String.check(Schema.isMaxLength(2e4)),
16
+ baseRevision: Revision,
17
+ headRevision: Revision,
18
+ changes: Schema.Array(ReviewChange).check(Schema.isMaxLength(100)),
19
+ unreviewedPaths: Schema.Array(ReviewPath).check(Schema.isMaxLength(300))
20
+ }) {};
21
+ const ReviewSeverity = Schema.Literals([
22
+ "blocking",
23
+ "important",
24
+ "nit"
25
+ ]);
26
+ /** A model-claimed problem kind used only to label findings for readers. */
27
+ const ReviewCategory = Schema.Literals([
28
+ "correctness",
29
+ "security",
30
+ "concurrency",
31
+ "performance",
32
+ "resources",
33
+ "error-handling",
34
+ "testing",
35
+ "maintainability",
36
+ "style",
37
+ "docs"
38
+ ]);
39
+ /** One actionable defect. `line` is a RIGHT-side line in the supplied patch. */
40
+ var ReviewFinding = class extends Schema.Class("@effect-agent/pr-review/ReviewFinding")({
41
+ path: ReviewPath,
42
+ line: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0))),
43
+ severity: ReviewSeverity,
44
+ /** Presentation label only; it never changes review admission or failure policy. */
45
+ category: ReviewCategory,
46
+ title: Schema.NonEmptyString.check(Schema.isMaxLength(200)),
47
+ body: Schema.NonEmptyString.check(Schema.isMaxLength(2e3))
48
+ }) {};
49
+ /** The only model-authored output. An empty findings array is a successful review. */
50
+ var ReviewReport = class extends Schema.Class("@effect-agent/pr-review/ReviewReport")({
51
+ summary: Schema.NonEmptyString.check(Schema.isMaxLength(6e3)),
52
+ findings: Schema.Array(ReviewFinding).check(Schema.isMaxLength(12))
53
+ }) {};
54
+ var ReviewUsage = class extends Schema.Class("@effect-agent/pr-review/ReviewUsage")({
55
+ inputTokens: Schema.Natural,
56
+ outputTokens: Schema.Natural
57
+ }) {};
58
+ var ReviewOutcome = class extends Schema.Class("@effect-agent/pr-review/ReviewOutcome")({
59
+ report: ReviewReport,
60
+ turns: Schema.Natural,
61
+ usage: ReviewUsage
55
62
  }) {};
56
- const fanOutReviewerProfile = FanOutReviewerProfile.make({
57
- deploymentClass: "E",
58
- readOnlyToolSurface: true,
59
- publicationOutsideAgentLoop: true,
60
- anchorsValidatedBeforePublication: true,
61
- hostScheduledPasses: true,
62
- failedPassesRetriedOnceThenCarried: true,
63
- hostOwnedRiskClassification: true,
64
- redundantHighRiskDiscovery: true,
65
- independentCandidateVerification: true,
66
- defectAbsenceProven: false,
67
- liveProfileOptIn: true,
68
- exactlyOnceExternalEffects: false
63
+ const BASE_INSTRUCTIONS = `Review the supplied pull-request diff once.
64
+
65
+ Report only concrete correctness, security, reliability, or maintainability defects that the author should act on. Do not praise, restate the change, invent missing repository context, or ask for speculative cleanup. An empty findings array is valid.
66
+
67
+ Every finding must use an exact supplied path. Set line only to a RIGHT-side added or context line visible in that path's unified patch; otherwise omit line. Use blocking only for a defect that should prevent shipping. Classify each finding with the closest available category. Treat unreviewedPaths as unavailable scope and never imply that you inspected it.`;
68
+ const reviewPolicy = AgentPolicy.make({
69
+ maxTurns: 1,
70
+ maxToolCalls: 1,
71
+ maxDuration: "5 minutes",
72
+ toolConcurrency: 1,
73
+ tokenBudget: 56e3,
74
+ completionReserveTokens: 8e3,
75
+ contextTokenLimit: 48e3,
76
+ onExhaustion: "fail"
77
+ });
78
+ const makeDefinition = (guidance) => Agent.define("pr-review", {
79
+ input: ReviewRequest,
80
+ output: ReviewReport,
81
+ instructions: guidance === void 0 || guidance.trim().length === 0 ? BASE_INSTRUCTIONS : `${BASE_INSTRUCTIONS}\n\nRepository guidance:\n${guidance.trim()}`,
82
+ toolkit: Toolkit.empty,
83
+ policy: reviewPolicy,
84
+ description: "Review one supplied pull-request diff in a single model call.",
85
+ metadata: {
86
+ deploymentClass: "E",
87
+ surface: "read-only"
88
+ }
89
+ });
90
+ const reviewBudgetLimits = UsageBudgetLimits.make({
91
+ maxInputTokens: 48e3,
92
+ maxOutputTokens: 8e3,
93
+ maxToolCalls: 0,
94
+ maxDurationMillis: 3e5
69
95
  });
70
- const LIVE_GATE_ENV = "EFFECT_AGENT_LIVE";
96
+ /** Return every RIGHT-side line on which GitHub can place a diff comment. */
97
+ const commentableLines = (patch) => {
98
+ const lines = /* @__PURE__ */ new Set();
99
+ let right;
100
+ for (const text of patch.split("\n")) {
101
+ const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(text);
102
+ if (hunk !== null) {
103
+ right = Number(hunk[1]);
104
+ continue;
105
+ }
106
+ if (right === void 0 || text.startsWith("\\")) continue;
107
+ if (text.startsWith("-")) continue;
108
+ if (text.startsWith("+") || text.startsWith(" ")) {
109
+ lines.add(right);
110
+ right += 1;
111
+ }
112
+ }
113
+ return lines;
114
+ };
115
+ const isCommentableLine = (patch, line) => commentableLines(patch).has(line);
71
116
  /**
72
- * `EFFECT_AGENT_LIVE=1` plus the named credential is the only enabling
73
- * combination for live (network, billed) profiles.
117
+ * Treat model output as untrusted: remove unknown paths, demote invalid line
118
+ * anchors to top-level findings, and collapse exact duplicates.
74
119
  */
75
- const liveProfileEnabled = (env, credentialEnv) => env["EFFECT_AGENT_LIVE"] === "1" && (env[credentialEnv] ?? "") !== "";
120
+ const sanitizeReviewReport = (request, report) => {
121
+ const patches = new Map(request.changes.map((change) => [change.path, change.patch]));
122
+ const seen = /* @__PURE__ */ new Set();
123
+ const findings = [];
124
+ for (const finding of report.findings) {
125
+ const patch = patches.get(finding.path);
126
+ if (patch === void 0) continue;
127
+ const line = finding.line !== void 0 && isCommentableLine(patch, finding.line) ? finding.line : void 0;
128
+ const key = `${finding.path}\u0000${String(line ?? "")}\u0000${finding.title.trim().toLowerCase()}`;
129
+ if (seen.has(key)) continue;
130
+ seen.add(key);
131
+ findings.push(ReviewFinding.make({
132
+ path: finding.path,
133
+ ...line === void 0 ? {} : { line },
134
+ severity: finding.severity,
135
+ category: finding.category,
136
+ title: finding.title,
137
+ body: finding.body
138
+ }));
139
+ }
140
+ return ReviewReport.make({
141
+ summary: report.summary,
142
+ findings
143
+ });
144
+ };
145
+ /** Build a provider-neutral reviewer. The returned `review` performs exactly one Run. */
146
+ const makeReviewer = (options) => {
147
+ const definition = makeDefinition(options.guidance);
148
+ const binding = Agent.withModel(definition, options.model);
149
+ const review = (request) => Effect.gen(function* () {
150
+ const budget = yield* makeUsageBudget(reviewBudgetLimits);
151
+ const result = yield* AgentRuntime.run(binding, request, { budget: toRunBudgetHook(budget) });
152
+ const usage = yield* budget.snapshot;
153
+ return ReviewOutcome.make({
154
+ report: sanitizeReviewReport(request, result.output),
155
+ turns: result.turns,
156
+ usage: ReviewUsage.make({
157
+ inputTokens: usage.inputTokens,
158
+ outputTokens: usage.outputTokens
159
+ })
160
+ });
161
+ }).pipe(Effect.provide(IdGenerator.layer), Effect.scoped);
162
+ return {
163
+ definition,
164
+ binding,
165
+ review
166
+ };
167
+ };
76
168
  //#endregion
77
- export { AGENT_PROMPT_PREAMBLE, AUTHORIZED_ADJUDICATION_ASSOCIATIONS, AdjudicableThread, AdjudicationComment, AdjudicationDisposition, CandidateAssessment, ChangedFile, ChangedFileStatus, ChangedFileSummary, ChangedFilesView, ChangedPath, CodeReview, ConcernCandidate, DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN, DEFAULT_MODEL, DEFAULT_PROVIDER, DiscoveredConcern, EFFORT_ALIASES, FINGERPRINT_MARKER_LENGTH, FailedReviewPass, FanOutReviewerProfile, FileDiffQuery, FileDiffView, FileReviewBrief, FileReviewEvidence, FileReviewReport, FileReviewToolkit, FileReviewer, FileSlice, FileSliceQuery, FindingCandidate, FindingCategory, FindingSeverity, GitCommitSha, GitHubApiFailure, GitHubEventWire, GitHubReviewTarget, INLINE_FINDING_TITLE_PATTERN, InvalidEffortInput, LIVE_GATE_ENV, ListChangedFiles, ListChangedFilesQuery, MAX_CHANGED_FILES, MAX_CHILD_CONCERNS, MAX_CHILD_FINDINGS, MAX_CONCERNS, MAX_FILE_CHARS, MAX_FILE_REVIEW_TOOL_CALLS, MAX_FINDINGS, MAX_MERGED_FINDINGS, MAX_PATCH_CHARS, MAX_REPORTED_UNASSIGNED_EVIDENCE_SHARDS, MAX_REVIEW_CHILDREN, MAX_REVIEW_CONTENT_CHARS, MAX_REVIEW_STATE_MARKER_CHARS, MAX_REVIEW_UNITS, MAX_STORED_ADJUDICATIONS, MAX_STORED_UNREVIEWED_PASSES, MAX_STORED_UNREVIEWED_PATHS, MAX_THREAD_ADJUDICATION_COMMANDS, MAX_TREE_COMPARISON_PATHS, MAX_UNIT_CANDIDATES, MAX_UNIT_EVIDENCE_SHARDS, MAX_UNIT_FILES, MAX_WALKTHROUGH_ENTRIES, MAX_WALKTHROUGH_SUMMARY_CHARS, OpenAiServiceTier, PROGRESS_COMMENT_MARKER_PREFIX, PROVIDER_CREDENTIAL_ENV, PROVIDER_EFFORT_RUNGS, PrReview, PriorReviewLookupFailure, PriorReviews, PublishedReview, PullRequestMetadata, PullRequestReviewer, PullRequestReviewerProfile, PullRequestSource, PullRequestSourceFailure, REVIEW_TOOL_RESULT_MAX_BYTES, REVIEW_UNIT_CONCURRENCY, ReadFile, ReadFileDiff, RetirableReview, RetirableReviewComment, ReviewAdjudicationFailure, ReviewAdjudicationHost, ReviewAssurance, ReviewCandidate, ReviewCandidateId, ReviewCommentDraft, ReviewConcern, ReviewDiscoveryPass, ReviewDiscoveryPerspective, ReviewEvent, ReviewEvidenceShard, ReviewEvidenceShardId, ReviewExecutionContext, ReviewFinding, ReviewHeadComparison, ReviewInputCoverage, ReviewInputViolation, ReviewMission, ReviewMode, ReviewPassId, ReviewPassMisbehaved, ReviewProgressReporter, ReviewProvider, ReviewPublicationPlan, ReviewPublisher, ReviewRetirementFailure, ReviewRetirementHost, ReviewRetirementReport, ReviewRiskCategory, ReviewRunOutcome, ReviewScopeMode, ReviewState, ReviewStateAuthenticationFailure, ReviewStateAuthenticator, ReviewStateMarker, ReviewStateMarkerTooLarge, ReviewTargetUnresolved, ReviewToolkit, ReviewToolkitLayer, ReviewTreeComparison, ReviewUnit, ReviewUnitId, ReviewUnitPlan, ReviewVerdict, ReviewWorkPerspective, ReviewWorkPhase, StoredAdjudication, StoredReviewConcern, StoredReviewFinding, StoredUnreviewedPass, UNIT_EVIDENCE_CHAR_BUDGET, UnreviewedStage, UnsupportedServiceTierProvider, WalkthroughEntry, adjudicationIdentity, anchorViolation, annotatePatch, anthropicClientLayer, assessFlatReview, assessmentSettlesSuggestionExactly, boundedListReason, buildPriorReviewContext, buildProfileMission, buildReviewMission, clampMaxFindings, classifyReviewRisks, collectReviewAdjudications, commentableLines, compactReviewLogger, compactReviewLoggingLayer, compileIgnoreGlobs, computeChangesetFingerprint, computeProfileFingerprint, concernIdentity, confirmedFindingForPublication, decideReviewRetirement, defaultFileReviewerPolicy, defaultReviewPolicy, deriveAdjudications, describeReviewModel, enforceFindingsBound, estimateReviewEffort, executeFanOutReview, executeReview, extractFingerprint, fanOutInputCoverage, fanOutReviewBudgetLimits, fanOutReviewerProfile, fileDiffView, fileReviewEvidenceChunks, fileReviewerInstructions, findingAnchorInUnitEvidence, findingIdentity, fingerprintUnchanged, flatAssurance, formatCompactLogLine, fromStoredConcern, fromStoredFinding, fullReviewExecutionContextLayer, fullReviewSelection, gitHubPriorReviewsLayer, gitHubPullRequestSourceLayer, gitHubReviewAdjudicationHostLayer, gitHubReviewLayers, gitHubReviewProgressLayer, gitHubReviewPublisherLayer, gitHubReviewRetirementHostLayer, hasReviewMetadataMarker, hasReviewableContent, ignoringPullRequestSourceLayer, isEffortPosition, isLineageAncestor, isReviewableFile, listChangedFilesHandler, liveProfileEnabled, makeAnthropicReviewModel, makeFileReviewerDefinition, makeFileReviewerInstructions, makeOpenAiReviewModel, makeReviewInstructions, mergeAdjudications, noReviewAdjudicationHost, noReviewAdjudicationHostLayer, noopReviewProgressReporterLayer, normalizeRepoRelativePath, openAiClientLayer, parseEffortPosition, parseGitHubSubmittedAt, parseIssueAdjudication, parsePatch, parseProgressClaim, parseThreadAdjudication, planPublication, planReviewUnits, planWalkthrough, pullRequestReviewerProfile, rankAndDedupeConcerns, rankAndDedupeFindings, readFileDiffHandler, readFileHandler, readGitHubEvent, renderAdjudicationContextLine, renderAgentPrompt, renderFingerprintMarker, renderPriorFindingContextLine, renderProgressBeginBody, renderProgressClaimMarker, renderProgressSettleBody, renderReviewContent, resolveEffortRung, resolveGuidance, resolveReviewTarget, retireStaleReviews, reviewBudgetLimits, reviewCandidateSubjectKey, reviewConcernKey, reviewInstructions, runFanOutReview, selectReviewRange, selectedPullRequestSourceLayer, splitCarriedScope, threadFindingTarget, toStoredConcern, toStoredFinding, unavailableReviewStateAuthenticatorLayer, validateReviewServiceTier, validateReviewState, webCryptoReviewStateAuthenticatorLayer };
169
+ export { ReviewCategory, ReviewChange, ReviewFinding, ReviewOutcome, ReviewReport, ReviewRequest, ReviewSeverity, ReviewUsage, commentableLines, isCommentableLine, makeReviewer, reviewBudgetLimits, reviewPolicy, sanitizeReviewReport };
78
170
 
79
171
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/internal/profiles.ts"],"sourcesContent":["import { Schema } from \"effect\";\n\n// ---------------------------------------------------------------------------\n// Committed capability claims, schema-first: what this package's reviewers\n// promise and — just as deliberately — what they never claim. Deployment\n// class E (ephemeral): one bounded AgentRuntime.run per invocation, no\n// durability, no exactly-once external effects (DUR-003).\n// ---------------------------------------------------------------------------\n\n/** The flat reviewer's committed capability claim. */\nexport class PullRequestReviewerProfile extends Schema.Class<PullRequestReviewerProfile>(\n \"@effect-agent/pr-review/PullRequestReviewerProfile\",\n)({\n /** Ephemeral runtime: one bounded AgentRuntime.run per invocation. */\n deploymentClass: Schema.Literal(\"E\"),\n /** Every model-callable tool is a read of the pull request; none mutate. */\n readOnlyToolSurface: Schema.Literal(true),\n /** The review is posted by the host AFTER the run settles, never by a tool. */\n publicationOutsideAgentLoop: Schema.Literal(true),\n /** Finding anchors are validated against the parsed diff before posting. */\n anchorsValidatedBeforePublication: Schema.Literal(true),\n /** The live profile is env-gated out of every ordinary test gate. */\n liveProfileOptIn: Schema.Literal(true),\n /** Never claimed at any phase (DUR-003). */\n exactlyOnceExternalEffects: Schema.Literal(false),\n}) {}\n\nexport const pullRequestReviewerProfile = PullRequestReviewerProfile.make({\n deploymentClass: \"E\",\n readOnlyToolSurface: true,\n publicationOutsideAgentLoop: true,\n anchorsValidatedBeforePublication: true,\n liveProfileOptIn: true,\n exactlyOnceExternalEffects: false,\n});\n\n/** The fan-out reviewer's committed capability claim, schema-first. */\nexport class FanOutReviewerProfile extends Schema.Class<FanOutReviewerProfile>(\n \"@effect-agent/pr-review/FanOutReviewerProfile\",\n)({\n /** Ephemeral runtime: bounded host-scheduled child runs per invocation. */\n deploymentClass: Schema.Literal(\"E\"),\n /** Every model-callable surface is evidence-only; children expose no tools. */\n readOnlyToolSurface: Schema.Literal(true),\n /** The review is posted by the host AFTER the run settles, never by a tool. */\n publicationOutsideAgentLoop: Schema.Literal(true),\n /** Child findings are untrusted; anchors are validated against the parsed diff. */\n anchorsValidatedBeforePublication: Schema.Literal(true),\n /** Host code schedules every pass from the deterministic plan; no coordinator model. */\n hostScheduledPasses: Schema.Literal(true),\n /** A failed pass is retried once, then reported and carried as unreviewed scope. */\n failedPassesRetriedOnceThenCarried: Schema.Literal(true),\n /** Risk categories and required specialist passes are pure host policy. */\n hostOwnedRiskClassification: Schema.Literal(true),\n /** Every host-classified high-risk unit receives a fresh specialist pass. */\n redundantHighRiskDiscovery: Schema.Literal(true),\n /** Only exact candidates confirmed by a fresh verifier child may publish. */\n independentCandidateVerification: Schema.Literal(true),\n /** No bounded model pipeline proves that a pull request is defect-free. */\n defectAbsenceProven: Schema.Literal(false),\n /** The live profile is env-gated out of every ordinary test gate. */\n liveProfileOptIn: Schema.Literal(true),\n /** Never claimed at any phase (DUR-003). */\n exactlyOnceExternalEffects: Schema.Literal(false),\n}) {}\n\nexport const fanOutReviewerProfile = FanOutReviewerProfile.make({\n deploymentClass: \"E\",\n readOnlyToolSurface: true,\n publicationOutsideAgentLoop: true,\n anchorsValidatedBeforePublication: true,\n hostScheduledPasses: true,\n failedPassesRetriedOnceThenCarried: true,\n hostOwnedRiskClassification: true,\n redundantHighRiskDiscovery: true,\n independentCandidateVerification: true,\n defectAbsenceProven: false,\n liveProfileOptIn: true,\n exactlyOnceExternalEffects: false,\n});\n\nexport const LIVE_GATE_ENV = \"EFFECT_AGENT_LIVE\";\n\n/**\n * `EFFECT_AGENT_LIVE=1` plus the named credential is the only enabling\n * combination for live (network, billed) profiles.\n */\nexport const liveProfileEnabled = (\n env: Record<string, string | undefined>,\n credentialEnv: string,\n): boolean => env[LIVE_GATE_ENV] === \"1\" && (env[credentialEnv] ?? \"\") !== \"\";\n"],"mappings":";;;;;;AAUA,IAAa,6BAAb,cAAgD,OAAO,MACrD,oDACF,CAAC,CAAC;;CAEA,iBAAiB,OAAO,QAAQ,GAAG;;CAEnC,qBAAqB,OAAO,QAAQ,IAAI;;CAExC,6BAA6B,OAAO,QAAQ,IAAI;;CAEhD,mCAAmC,OAAO,QAAQ,IAAI;;CAEtD,kBAAkB,OAAO,QAAQ,IAAI;;CAErC,4BAA4B,OAAO,QAAQ,KAAK;AAClD,CAAC,CAAC,CAAC,CAAC;AAEJ,MAAa,6BAA6B,2BAA2B,KAAK;CACxE,iBAAiB;CACjB,qBAAqB;CACrB,6BAA6B;CAC7B,mCAAmC;CACnC,kBAAkB;CAClB,4BAA4B;AAC9B,CAAC;;AAGD,IAAa,wBAAb,cAA2C,OAAO,MAChD,+CACF,CAAC,CAAC;;CAEA,iBAAiB,OAAO,QAAQ,GAAG;;CAEnC,qBAAqB,OAAO,QAAQ,IAAI;;CAExC,6BAA6B,OAAO,QAAQ,IAAI;;CAEhD,mCAAmC,OAAO,QAAQ,IAAI;;CAEtD,qBAAqB,OAAO,QAAQ,IAAI;;CAExC,oCAAoC,OAAO,QAAQ,IAAI;;CAEvD,6BAA6B,OAAO,QAAQ,IAAI;;CAEhD,4BAA4B,OAAO,QAAQ,IAAI;;CAE/C,kCAAkC,OAAO,QAAQ,IAAI;;CAErD,qBAAqB,OAAO,QAAQ,KAAK;;CAEzC,kBAAkB,OAAO,QAAQ,IAAI;;CAErC,4BAA4B,OAAO,QAAQ,KAAK;AAClD,CAAC,CAAC,CAAC,CAAC;AAEJ,MAAa,wBAAwB,sBAAsB,KAAK;CAC9D,iBAAiB;CACjB,qBAAqB;CACrB,6BAA6B;CAC7B,mCAAmC;CACnC,qBAAqB;CACrB,oCAAoC;CACpC,6BAA6B;CAC7B,4BAA4B;CAC5B,kCAAkC;CAClC,qBAAqB;CACrB,kBAAkB;CAClB,4BAA4B;AAC9B,CAAC;AAED,MAAa,gBAAgB;;;;;AAM7B,MAAa,sBACX,KACA,kBACY,IAAA,yBAAuB,QAAQ,IAAI,kBAAkB,QAAQ"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/review.ts"],"sourcesContent":["import { Effect, Schema } from \"effect\";\nimport {\n Agent,\n AgentPolicy,\n AgentRuntime,\n IdGenerator,\n makeUsageBudget,\n toRunBudgetHook,\n UsageBudgetLimits,\n} from \"effect-agent\";\nimport { type LanguageModel, type Model, Toolkit } from \"effect/unstable/ai\";\n\nconst ReviewPath = Schema.NonEmptyString.check(Schema.isMaxLength(512));\nconst Revision = Schema.NonEmptyString.check(Schema.isMaxLength(128));\n\n/** One complete textual patch supplied by the host. */\nexport class ReviewChange extends Schema.Class<ReviewChange>(\n \"@effect-agent/pr-review/ReviewChange\",\n)({\n path: ReviewPath,\n patch: Schema.NonEmptyString.check(Schema.isMaxLength(80_000)),\n}) {}\n\n/** The provider-neutral input to one review pass. */\nexport class ReviewRequest extends Schema.Class<ReviewRequest>(\n \"@effect-agent/pr-review/ReviewRequest\",\n)({\n title: Schema.String.check(Schema.isMaxLength(1_000)),\n description: Schema.String.check(Schema.isMaxLength(20_000)),\n baseRevision: Revision,\n headRevision: Revision,\n changes: Schema.Array(ReviewChange).check(Schema.isMaxLength(100)),\n unreviewedPaths: Schema.Array(ReviewPath).check(Schema.isMaxLength(300)),\n}) {}\n\nexport const ReviewSeverity = Schema.Literals([\"blocking\", \"important\", \"nit\"]);\nexport type ReviewSeverity = typeof ReviewSeverity.Type;\n\n/** A model-claimed problem kind used only to label findings for readers. */\nexport const ReviewCategory = Schema.Literals([\n \"correctness\",\n \"security\",\n \"concurrency\",\n \"performance\",\n \"resources\",\n \"error-handling\",\n \"testing\",\n \"maintainability\",\n \"style\",\n \"docs\",\n]);\nexport type ReviewCategory = typeof ReviewCategory.Type;\n\n/** One actionable defect. `line` is a RIGHT-side line in the supplied patch. */\nexport class ReviewFinding extends Schema.Class<ReviewFinding>(\n \"@effect-agent/pr-review/ReviewFinding\",\n)({\n path: ReviewPath,\n line: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0))),\n severity: ReviewSeverity,\n /** Presentation label only; it never changes review admission or failure policy. */\n category: ReviewCategory,\n title: Schema.NonEmptyString.check(Schema.isMaxLength(200)),\n body: Schema.NonEmptyString.check(Schema.isMaxLength(2_000)),\n}) {}\n\n/** The only model-authored output. An empty findings array is a successful review. */\nexport class ReviewReport extends Schema.Class<ReviewReport>(\n \"@effect-agent/pr-review/ReviewReport\",\n)({\n summary: Schema.NonEmptyString.check(Schema.isMaxLength(6_000)),\n findings: Schema.Array(ReviewFinding).check(Schema.isMaxLength(12)),\n}) {}\n\nexport class ReviewUsage extends Schema.Class<ReviewUsage>(\"@effect-agent/pr-review/ReviewUsage\")({\n inputTokens: Schema.Natural,\n outputTokens: Schema.Natural,\n}) {}\n\nexport class ReviewOutcome extends Schema.Class<ReviewOutcome>(\n \"@effect-agent/pr-review/ReviewOutcome\",\n)({\n report: ReviewReport,\n turns: Schema.Natural,\n usage: ReviewUsage,\n}) {}\n\nconst BASE_INSTRUCTIONS = `Review the supplied pull-request diff once.\n\nReport only concrete correctness, security, reliability, or maintainability defects that the author should act on. Do not praise, restate the change, invent missing repository context, or ask for speculative cleanup. An empty findings array is valid.\n\nEvery finding must use an exact supplied path. Set line only to a RIGHT-side added or context line visible in that path's unified patch; otherwise omit line. Use blocking only for a defect that should prevent shipping. Classify each finding with the closest available category. Treat unreviewedPaths as unavailable scope and never imply that you inspected it.`;\n\nexport const reviewPolicy = AgentPolicy.make({\n maxTurns: 1,\n maxToolCalls: 1,\n maxDuration: \"5 minutes\",\n toolConcurrency: 1,\n tokenBudget: 56_000,\n completionReserveTokens: 8_000,\n contextTokenLimit: 48_000,\n onExhaustion: \"fail\",\n});\n\nconst makeDefinition = (guidance?: string) =>\n Agent.define(\"pr-review\", {\n input: ReviewRequest,\n output: ReviewReport,\n instructions:\n guidance === undefined || guidance.trim().length === 0\n ? BASE_INSTRUCTIONS\n : `${BASE_INSTRUCTIONS}\\n\\nRepository guidance:\\n${guidance.trim()}`,\n toolkit: Toolkit.empty,\n policy: reviewPolicy,\n description: \"Review one supplied pull-request diff in a single model call.\",\n metadata: { deploymentClass: \"E\", surface: \"read-only\" },\n });\n\nexport const reviewBudgetLimits = UsageBudgetLimits.make({\n maxInputTokens: 48_000,\n maxOutputTokens: 8_000,\n maxToolCalls: 0,\n maxDurationMillis: 300_000,\n});\n\n/** Return every RIGHT-side line on which GitHub can place a diff comment. */\nexport const commentableLines = (patch: string): ReadonlySet<number> => {\n const lines = new Set<number>();\n let right: number | undefined;\n for (const text of patch.split(\"\\n\")) {\n const hunk = /^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/.exec(text);\n if (hunk !== null) {\n right = Number(hunk[1]);\n continue;\n }\n if (right === undefined || text.startsWith(\"\\\\\")) continue;\n if (text.startsWith(\"-\")) continue;\n if (text.startsWith(\"+\") || text.startsWith(\" \")) {\n lines.add(right);\n right += 1;\n }\n }\n return lines;\n};\n\nexport const isCommentableLine = (patch: string, line: number): boolean =>\n commentableLines(patch).has(line);\n\n/**\n * Treat model output as untrusted: remove unknown paths, demote invalid line\n * anchors to top-level findings, and collapse exact duplicates.\n */\nexport const sanitizeReviewReport = (\n request: Pick<ReviewRequest, \"changes\">,\n report: ReviewReport,\n): ReviewReport => {\n const patches = new Map(request.changes.map((change) => [change.path, change.patch] as const));\n const seen = new Set<string>();\n const findings: Array<ReviewFinding> = [];\n for (const finding of report.findings) {\n const patch = patches.get(finding.path);\n if (patch === undefined) continue;\n const line =\n finding.line !== undefined && isCommentableLine(patch, finding.line)\n ? finding.line\n : undefined;\n const key = `${finding.path}\\u0000${String(line ?? \"\")}\\u0000${finding.title.trim().toLowerCase()}`;\n if (seen.has(key)) continue;\n seen.add(key);\n findings.push(\n ReviewFinding.make({\n path: finding.path,\n ...(line === undefined ? {} : { line }),\n severity: finding.severity,\n category: finding.category,\n title: finding.title,\n body: finding.body,\n }),\n );\n }\n return ReviewReport.make({ summary: report.summary, findings });\n};\n\nexport interface ReviewerOptions<Provider, ModelProvides, ModelRequires> {\n readonly model: Model.Model<Provider, LanguageModel.LanguageModel | ModelProvides, ModelRequires>;\n readonly guidance?: string | undefined;\n}\n\n/** Build a provider-neutral reviewer. The returned `review` performs exactly one Run. */\nexport const makeReviewer = <Provider, ModelProvides, ModelRequires>(\n options: ReviewerOptions<Provider, ModelProvides, ModelRequires>,\n) => {\n const definition = makeDefinition(options.guidance);\n const binding = Agent.withModel(definition, options.model);\n const review = (request: ReviewRequest) =>\n Effect.gen(function* () {\n const budget = yield* makeUsageBudget(reviewBudgetLimits);\n const result = yield* AgentRuntime.run(binding, request, {\n budget: toRunBudgetHook(budget),\n });\n const usage = yield* budget.snapshot;\n return ReviewOutcome.make({\n report: sanitizeReviewReport(request, result.output),\n turns: result.turns,\n usage: ReviewUsage.make({\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n }),\n });\n }).pipe(Effect.provide(IdGenerator.layer), Effect.scoped);\n return { definition, binding, review } as const;\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,32 +1,17 @@
1
1
  {
2
2
  "name": "@effect-agent/pr-review",
3
- "version": "0.1.0-beta.28",
3
+ "version": "0.1.0-beta.29",
4
4
  "exports": {
5
5
  ".": {
6
6
  "types": "./dist/index.d.mts",
7
7
  "default": "./dist/index.mjs"
8
- },
9
- "./testing": {
10
- "types": "./dist/testing.d.mts",
11
- "default": "./dist/testing.mjs"
12
- },
13
- "./action": {
14
- "types": "./dist/action.d.mts",
15
- "default": "./dist/action.mjs"
16
- },
17
- "./cli": {
18
- "types": "./dist/cli.d.mts",
19
- "default": "./dist/cli.mjs"
20
8
  }
21
9
  },
22
10
  "dependencies": {
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
11
  "effect": "4.0.0-rc.110",
27
- "effect-agent": "0.1.0-beta.28"
12
+ "effect-agent": "0.1.0-beta.29"
28
13
  },
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.",
14
+ "description": "A provider-neutral, single-pass pull-request review agent.",
30
15
  "license": "MIT",
31
16
  "repository": {
32
17
  "type": "git",
package/src/index.ts CHANGED
@@ -1,25 +1 @@
1
- // The @effect-agent/pr-review public surface. Platform-free: host entrypoints
2
- // live behind the "./action" and "./cli" subpaths, deterministic test helpers
3
- // behind "./testing".
4
- export * from "./internal/adjudication.ts";
5
- export * from "./internal/anchors.ts";
6
- export * from "./internal/diff.ts";
7
- export * from "./internal/coverage.ts";
8
- export * from "./internal/effort.ts";
9
- export * from "./internal/factory.ts";
10
- export * from "./internal/fan-out.ts";
11
- export * from "./internal/fingerprint.ts";
12
- export * from "./internal/github.ts";
13
- export * from "./internal/github-env.ts";
14
- export * from "./internal/ignore.ts";
15
- export * from "./internal/logging.ts";
16
- export * from "./internal/profiles.ts";
17
- export * from "./internal/progress.ts";
18
- export * from "./internal/providers.ts";
19
- export * from "./internal/render.ts";
20
- export * from "./internal/retirement.ts";
21
- export * from "./internal/review-agent.ts";
22
- export * from "./internal/review-state.ts";
23
- export * from "./internal/review-units.ts";
24
- export * from "./internal/run.ts";
25
- export * from "./internal/source.ts";
1
+ export * from "./review.ts";
package/src/review.ts ADDED
@@ -0,0 +1,212 @@
1
+ import { Effect, Schema } from "effect";
2
+ import {
3
+ Agent,
4
+ AgentPolicy,
5
+ AgentRuntime,
6
+ IdGenerator,
7
+ makeUsageBudget,
8
+ toRunBudgetHook,
9
+ UsageBudgetLimits,
10
+ } from "effect-agent";
11
+ import { type LanguageModel, type Model, Toolkit } from "effect/unstable/ai";
12
+
13
+ const ReviewPath = Schema.NonEmptyString.check(Schema.isMaxLength(512));
14
+ const Revision = Schema.NonEmptyString.check(Schema.isMaxLength(128));
15
+
16
+ /** One complete textual patch supplied by the host. */
17
+ export class ReviewChange extends Schema.Class<ReviewChange>(
18
+ "@effect-agent/pr-review/ReviewChange",
19
+ )({
20
+ path: ReviewPath,
21
+ patch: Schema.NonEmptyString.check(Schema.isMaxLength(80_000)),
22
+ }) {}
23
+
24
+ /** The provider-neutral input to one review pass. */
25
+ export class ReviewRequest extends Schema.Class<ReviewRequest>(
26
+ "@effect-agent/pr-review/ReviewRequest",
27
+ )({
28
+ title: Schema.String.check(Schema.isMaxLength(1_000)),
29
+ description: Schema.String.check(Schema.isMaxLength(20_000)),
30
+ baseRevision: Revision,
31
+ headRevision: Revision,
32
+ changes: Schema.Array(ReviewChange).check(Schema.isMaxLength(100)),
33
+ unreviewedPaths: Schema.Array(ReviewPath).check(Schema.isMaxLength(300)),
34
+ }) {}
35
+
36
+ export const ReviewSeverity = Schema.Literals(["blocking", "important", "nit"]);
37
+ export type ReviewSeverity = typeof ReviewSeverity.Type;
38
+
39
+ /** A model-claimed problem kind used only to label findings for readers. */
40
+ export const ReviewCategory = Schema.Literals([
41
+ "correctness",
42
+ "security",
43
+ "concurrency",
44
+ "performance",
45
+ "resources",
46
+ "error-handling",
47
+ "testing",
48
+ "maintainability",
49
+ "style",
50
+ "docs",
51
+ ]);
52
+ export type ReviewCategory = typeof ReviewCategory.Type;
53
+
54
+ /** One actionable defect. `line` is a RIGHT-side line in the supplied patch. */
55
+ export class ReviewFinding extends Schema.Class<ReviewFinding>(
56
+ "@effect-agent/pr-review/ReviewFinding",
57
+ )({
58
+ path: ReviewPath,
59
+ line: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0))),
60
+ severity: ReviewSeverity,
61
+ /** Presentation label only; it never changes review admission or failure policy. */
62
+ category: ReviewCategory,
63
+ title: Schema.NonEmptyString.check(Schema.isMaxLength(200)),
64
+ body: Schema.NonEmptyString.check(Schema.isMaxLength(2_000)),
65
+ }) {}
66
+
67
+ /** The only model-authored output. An empty findings array is a successful review. */
68
+ export class ReviewReport extends Schema.Class<ReviewReport>(
69
+ "@effect-agent/pr-review/ReviewReport",
70
+ )({
71
+ summary: Schema.NonEmptyString.check(Schema.isMaxLength(6_000)),
72
+ findings: Schema.Array(ReviewFinding).check(Schema.isMaxLength(12)),
73
+ }) {}
74
+
75
+ export class ReviewUsage extends Schema.Class<ReviewUsage>("@effect-agent/pr-review/ReviewUsage")({
76
+ inputTokens: Schema.Natural,
77
+ outputTokens: Schema.Natural,
78
+ }) {}
79
+
80
+ export class ReviewOutcome extends Schema.Class<ReviewOutcome>(
81
+ "@effect-agent/pr-review/ReviewOutcome",
82
+ )({
83
+ report: ReviewReport,
84
+ turns: Schema.Natural,
85
+ usage: ReviewUsage,
86
+ }) {}
87
+
88
+ const BASE_INSTRUCTIONS = `Review the supplied pull-request diff once.
89
+
90
+ Report only concrete correctness, security, reliability, or maintainability defects that the author should act on. Do not praise, restate the change, invent missing repository context, or ask for speculative cleanup. An empty findings array is valid.
91
+
92
+ Every finding must use an exact supplied path. Set line only to a RIGHT-side added or context line visible in that path's unified patch; otherwise omit line. Use blocking only for a defect that should prevent shipping. Classify each finding with the closest available category. Treat unreviewedPaths as unavailable scope and never imply that you inspected it.`;
93
+
94
+ export const reviewPolicy = AgentPolicy.make({
95
+ maxTurns: 1,
96
+ maxToolCalls: 1,
97
+ maxDuration: "5 minutes",
98
+ toolConcurrency: 1,
99
+ tokenBudget: 56_000,
100
+ completionReserveTokens: 8_000,
101
+ contextTokenLimit: 48_000,
102
+ onExhaustion: "fail",
103
+ });
104
+
105
+ const makeDefinition = (guidance?: string) =>
106
+ Agent.define("pr-review", {
107
+ input: ReviewRequest,
108
+ output: ReviewReport,
109
+ instructions:
110
+ guidance === undefined || guidance.trim().length === 0
111
+ ? BASE_INSTRUCTIONS
112
+ : `${BASE_INSTRUCTIONS}\n\nRepository guidance:\n${guidance.trim()}`,
113
+ toolkit: Toolkit.empty,
114
+ policy: reviewPolicy,
115
+ description: "Review one supplied pull-request diff in a single model call.",
116
+ metadata: { deploymentClass: "E", surface: "read-only" },
117
+ });
118
+
119
+ export const reviewBudgetLimits = UsageBudgetLimits.make({
120
+ maxInputTokens: 48_000,
121
+ maxOutputTokens: 8_000,
122
+ maxToolCalls: 0,
123
+ maxDurationMillis: 300_000,
124
+ });
125
+
126
+ /** Return every RIGHT-side line on which GitHub can place a diff comment. */
127
+ export const commentableLines = (patch: string): ReadonlySet<number> => {
128
+ const lines = new Set<number>();
129
+ let right: number | undefined;
130
+ for (const text of patch.split("\n")) {
131
+ const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(text);
132
+ if (hunk !== null) {
133
+ right = Number(hunk[1]);
134
+ continue;
135
+ }
136
+ if (right === undefined || text.startsWith("\\")) continue;
137
+ if (text.startsWith("-")) continue;
138
+ if (text.startsWith("+") || text.startsWith(" ")) {
139
+ lines.add(right);
140
+ right += 1;
141
+ }
142
+ }
143
+ return lines;
144
+ };
145
+
146
+ export const isCommentableLine = (patch: string, line: number): boolean =>
147
+ commentableLines(patch).has(line);
148
+
149
+ /**
150
+ * Treat model output as untrusted: remove unknown paths, demote invalid line
151
+ * anchors to top-level findings, and collapse exact duplicates.
152
+ */
153
+ export const sanitizeReviewReport = (
154
+ request: Pick<ReviewRequest, "changes">,
155
+ report: ReviewReport,
156
+ ): ReviewReport => {
157
+ const patches = new Map(request.changes.map((change) => [change.path, change.patch] as const));
158
+ const seen = new Set<string>();
159
+ const findings: Array<ReviewFinding> = [];
160
+ for (const finding of report.findings) {
161
+ const patch = patches.get(finding.path);
162
+ if (patch === undefined) continue;
163
+ const line =
164
+ finding.line !== undefined && isCommentableLine(patch, finding.line)
165
+ ? finding.line
166
+ : undefined;
167
+ const key = `${finding.path}\u0000${String(line ?? "")}\u0000${finding.title.trim().toLowerCase()}`;
168
+ if (seen.has(key)) continue;
169
+ seen.add(key);
170
+ findings.push(
171
+ ReviewFinding.make({
172
+ path: finding.path,
173
+ ...(line === undefined ? {} : { line }),
174
+ severity: finding.severity,
175
+ category: finding.category,
176
+ title: finding.title,
177
+ body: finding.body,
178
+ }),
179
+ );
180
+ }
181
+ return ReviewReport.make({ summary: report.summary, findings });
182
+ };
183
+
184
+ export interface ReviewerOptions<Provider, ModelProvides, ModelRequires> {
185
+ readonly model: Model.Model<Provider, LanguageModel.LanguageModel | ModelProvides, ModelRequires>;
186
+ readonly guidance?: string | undefined;
187
+ }
188
+
189
+ /** Build a provider-neutral reviewer. The returned `review` performs exactly one Run. */
190
+ export const makeReviewer = <Provider, ModelProvides, ModelRequires>(
191
+ options: ReviewerOptions<Provider, ModelProvides, ModelRequires>,
192
+ ) => {
193
+ const definition = makeDefinition(options.guidance);
194
+ const binding = Agent.withModel(definition, options.model);
195
+ const review = (request: ReviewRequest) =>
196
+ Effect.gen(function* () {
197
+ const budget = yield* makeUsageBudget(reviewBudgetLimits);
198
+ const result = yield* AgentRuntime.run(binding, request, {
199
+ budget: toRunBudgetHook(budget),
200
+ });
201
+ const usage = yield* budget.snapshot;
202
+ return ReviewOutcome.make({
203
+ report: sanitizeReviewReport(request, result.output),
204
+ turns: result.turns,
205
+ usage: ReviewUsage.make({
206
+ inputTokens: usage.inputTokens,
207
+ outputTokens: usage.outputTokens,
208
+ }),
209
+ });
210
+ }).pipe(Effect.provide(IdGenerator.layer), Effect.scoped);
211
+ return { definition, binding, review } as const;
212
+ };