@effect-agent/pr-review 0.1.0-beta.27 → 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/README.md CHANGED
@@ -1,210 +1,15 @@
1
1
  # @effect-agent/pr-review
2
2
 
3
- A bounded, fail-closed GitHub pull-request reviewer built on effect-agent's public APIs. Read-only
4
- Agents review host-partitioned pull-request evidence
5
- through typed ports; the host validates every finding anchor against the real
6
- diff and posts one review after the run settles.
3
+ A small, provider-neutral review agent.
7
4
 
8
- **Deployment class E (ephemeral).** One `AgentRuntime.run` per invocation, no
9
- durability claim, and review posting is never exactly-once: a failed or
10
- truncated run posts nothing.
11
-
12
- ## Use
5
+ The package accepts an already-collected diff, makes one model call with no
6
+ tools, validates the returned paths and line anchors, and returns a structured
7
+ report with token usage. It knows nothing about GitHub, webhooks, comments,
8
+ providers, retries, or review history; those belong to the host channel.
9
+ Each finding carries a severity and problem category so a host can render a
10
+ scannable review without another model call.
13
11
 
14
12
  ```ts
15
- import { Effect, Layer } from "effect";
16
- import {
17
- PrReview,
18
- describeReviewModel,
19
- fullReviewExecutionContextLayer,
20
- gitHubReviewLayers,
21
- resolveReviewTarget,
22
- makeOpenAiReviewModel,
23
- openAiClientLayer,
24
- } from "@effect-agent/pr-review";
25
-
26
- const reviewer = PrReview.make({ model: makeOpenAiReviewModel() });
27
-
28
- const program = Effect.gen(function* () {
29
- const target = yield* resolveReviewTarget({ repository: "acme/api", number: 123 });
30
- return yield* reviewer
31
- .run({ post: true })
32
- .pipe(
33
- Effect.provide(
34
- fullReviewExecutionContextLayer("explicit direct full review").pipe(
35
- Layer.provideMerge(Layer.merge(gitHubReviewLayers(target), openAiClientLayer)),
36
- ),
37
- ),
38
- );
39
- });
13
+ const reviewer = makeReviewer({ model, guidance });
14
+ const outcome = yield * reviewer.review(request);
40
15
  ```
41
-
42
- The run's requirement channel keeps every real dependency visible: the
43
- `PullRequestSource`, `ReviewPublisher`, `ReviewAdjudicationHost`, and
44
- `ReviewExecutionContext` ports, the provider client, and the handler Layer of
45
- any extra tool you add. Direct callers provide an explicit full-review context
46
- as above; the packaged action supplies its selected incremental or full range.
47
- Anthropic is equally supported
48
- (`makeAnthropicReviewModel`, `anthropicClientLayer`), and the factory accepts
49
- any Effect AI Model.
50
-
51
- The built-in OpenAI binding can opt each request into Fast mode:
52
-
53
- ```ts
54
- const reviewer = PrReview.make({
55
- model: makeOpenAiReviewModel("gpt-5.6-sol", undefined, "fast"),
56
- modelLabel: describeReviewModel("openai", "gpt-5.6-sol", undefined, "fast"),
57
- });
58
- ```
59
-
60
- `fast` is the only packaged service-tier value. The helper sends it as the
61
- OpenAI Responses `service_tier`; omitting it leaves the request unset so the
62
- OpenAI project default applies. The Action and CLI reject a service tier when
63
- the selected provider is not OpenAI. The model label includes the tier and
64
- therefore changes both ordinary and profile fingerprints.
65
-
66
- ## Adapt
67
-
68
- ```ts
69
- const reviewer = PrReview.make({
70
- model: makeAnthropicReviewModel("claude-sonnet-5"),
71
- guidance: (mission) => [
72
- "This is an Effect codebase. Flag naked Promises in public APIs.",
73
- mission.changedFileCount > 50 ? "Large PR: prioritize breadth over nit depth." : "",
74
- ],
75
- ignore: ["**/*.lock", "dist/**"],
76
- maxFindings: 10,
77
- policy: { maxTurns: 20, maxToolCalls: 40, maxDuration: "10 minutes", toolConcurrency: 2 },
78
- extraTools: [MyReadonlyTool], // must be annotated ToolExecutionClass "readonly"
79
- });
80
- ```
81
-
82
- Model output is untrusted. The host revalidates anchors, enforces `maxFindings`, and publishes only
83
- after the run settles. `PrReview.makeFanOut` partitions bounded evidence and schedules general and
84
- specialist discovery for every unit, followed by independent candidate verification. Verifiers
85
- receive the exact claims and enough neighboring evidence to reject a plausible mistake, but no
86
- discovery reasoning.
87
-
88
- Each failed pass retries once, then carries its unit forward. The host accepts only confirmed
89
- candidate IDs, discards anchors outside assigned evidence, deduplicates claims in plan order, and
90
- derives the verdict from validated severities. Shared `guidance` reaches discovery and verification.
91
-
92
- When GitHub omits a textual patch, the source reads bounded, strict UTF-8 base/head content and
93
- labels it with non-anchorable `B`/`H` lines. Invalid UTF-8, binary NUL content, missing sides, and
94
- oversized files remain unreviewable and keep input coverage incomplete. Exclude them with ignore
95
- globs only when that is intentional.
96
-
97
- ## Assurance model
98
-
99
- The public result deliberately separates two claims:
100
-
101
- - **Input coverage** says every reviewable required path in the selected
102
- scope was assigned every deterministic bounded evidence shard and
103
- separately names partially assigned and over-capacity paths, the exact
104
- unassigned-shard count with a bounded identifier sample, undiffable paths,
105
- and source truncation. A large textual diff is split across shards and
106
- units instead of being called partial merely for exceeding one prompt
107
- chunk. Input coverage does not say the model understood the evidence.
108
- - **Review assurance** says every scheduled general discovery pass, required
109
- independent specialist pass, and candidate-verification pass settled after
110
- at most one retry. It reports discovered, confirmed, rejected, unsettled,
111
- and discarded (invalid-anchor) candidate counts plus failed pass IDs. A
112
- failed pass is a reviewer-side gap: its unit's paths carry forward as
113
- retryable scope and the next incremental run re-reviews exactly them.
114
-
115
- `settled` assurance means the configured work completed; it never means the
116
- review is exhaustive or the pull request is defect-free. Risk classification
117
- is deterministic host policy over paths and bounded text and intentionally
118
- favors redundant work, but it cannot recognize every semantically risky
119
- change. The specialist pass is context-independent redundancy, not a claim of
120
- provider or model diversity. Running it for every unit prevents classifier
121
- silence from suppressing scrutiny; the category labels still cannot prove
122
- that every semantic risk was recognized. Hosts and UI use `inputCoverage` and `assurance`.
123
- The flat reviewer has path-input accounting but no independent verifier, so
124
- its assurance is `unverified` and the Action check cannot report success from
125
- that shape.
126
-
127
- ## Maintainer adjudication
128
-
129
- A maintainer can settle a finding without changing code, from the pull request itself:
130
-
131
- - On a finding's inline thread, reply `/adjudicate accepted-risk|refuted|obsolete[: reason]`.
132
- The thread names the target, so the verb alone suffices.
133
- - For an unanchored concern, comment
134
- `/adjudicate <disposition> "<exact concern title>"[: reason]` in the PR conversation. The quoted
135
- title is required and must match exactly.
136
-
137
- An adjudicated identity leaves active findings, verdict counts, and the check conclusion; it
138
- renders in a collapsed "Adjudicated" section instead, and the final audit distinguishes fixed,
139
- adjudicated, and still-open items. The reviewer prompt names each adjudication so the model does
140
- not re-raise it without materially new evidence. Identity is exact — path, line range, and title
141
- for findings, title alone for concerns — so a materially different finding at the same location is
142
- untouched. Adjudications persist in the signed review state, and the skip-unchanged path re-reads
143
- them, so an adjudication lifts a blocking check without a new commit.
144
-
145
- **Authorization is fail-closed**: only comments whose `author_association` is OWNER, MEMBER, or
146
- COLLABORATOR adjudicate. Anything else — third parties, bots, malformed commands — is ignored
147
- (logged at debug). Later adjudications of the same identity win by comment creation order, bounded
148
- at 20 stored entries (oldest dropped with a logged notice). If either GitHub listing surface fails,
149
- or one thread exceeds the bounded authorized-command history, fresh collection is discarded and
150
- the stored set stands unchanged.
151
-
152
- **Rejected alternative — parsing free-text rebuttals** ("this is fine because …" replies): only an
153
- explicit, authorized verb is auditable and fail-closed. Inferring intent from prose would let model
154
- output or third-party comments silently suppress findings, and nobody could later say which comment
155
- dismissed what.
156
-
157
- ## What a posted review looks like
158
-
159
- The host derives callouts, statistics, walkthroughs, inline comments, and the final prompt from
160
- validated findings. Model prose cannot choose the verdict or smuggle paths outside the changeset.
161
- The [Action guide](../../action/README.md) documents the rendered review and workflow outputs.
162
-
163
- ## Swap a port
164
-
165
- Tools observe the pull request only through `PullRequestSource`; publication
166
- happens only through `ReviewPublisher`. Provide your own Layers to review
167
- any diff-shaped input or publish elsewhere. The GitHub REST adapters are
168
- one implementation, not the contract.
169
-
170
- ## Test what you adapted
171
-
172
- ```ts
173
- import {
174
- fixturePullRequestSourceLayer,
175
- collectingReviewPublisherLayer,
176
- makeOfflineReviewerModel,
177
- makePromptKeyedModel,
178
- } from "@effect-agent/pr-review/testing";
179
- ```
180
-
181
- Deterministic in-memory adapters for both ports plus prompt-keyed scripted
182
- models that call the real Tools. Tests need no network or credentials and exercise every ordinary
183
- gate.
184
-
185
- ## Action and CLI
186
-
187
- The prebuilt GitHub Action handles signed incremental state, fail-closed scope selection, sticky
188
- progress, stale-review retirement, and final audits. Failed stages remain attached to their exact
189
- unchanged paths; failed verification reopens discovery only for its paths because candidates are
190
- not persisted. See its [setup and behavior guide](../../action/README.md) and
191
- [input reference](../../action/action.yml). Custom hosts can import `runReviewAction` from
192
- `@effect-agent/pr-review/action`.
193
-
194
- For an amended or force-pushed head, the GitHub adapter compares complete commit tree snapshots
195
- across a bounded PR and continuity path set. Selection then uses the current full PR records, so
196
- patches and rename metadata remain intact. An unavailable, malformed, or truncated tree falls back
197
- to the full PR and records the cause in the review reason.
198
-
199
- Run the CLI with
200
- `bun src/cli.ts --repo owner/name --pr 123 [--post] [--provider anthropic] [--service-tier fast] [--fan-out]`.
201
-
202
- ## Bounds, spelled out
203
-
204
- Every definition has a finite `AgentPolicy` and run-level `UsageBudgetLimits`. The reviewer rejects
205
- file versions beyond 200k bytes or characters and patchless B/H renderings beyond 220k characters.
206
- It reads at most 300 changed files. Each fan-out unit holds at most 12 files, 12 complete evidence
207
- shards, and 240,000 evidence characters. At most eight units produce 24 attached children with
208
- child concurrency capped at four. The result reports every capacity overflow. Any blocking
209
- finding, input gap, failed pass, exhausted pass, mismatched candidate batch, or unsettled
210
- verification prevents a green check.