@effect-agent/pr-review 0.1.0-beta.22 → 0.1.0-beta.24

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 CHANGED
@@ -59,21 +59,26 @@ re-validated against the parsed unified diff (invalid ones are demoted into
59
59
  the review body with the reason named, never trusted), the findings bound is
60
60
  enforced host-side, and publication happens only through the
61
61
  `ReviewPublisher` port after the run settles. `PrReview.makeFanOut` builds the
62
- assured delegating variant. The host deterministically partitions bounded
62
+ assured host-scheduled variant. The host deterministically partitions bounded
63
63
  evidence, classifies high-risk units, requires a general discovery pass for
64
64
  every unit, adds a fresh specialist discovery pass for every unit, and uses
65
65
  deterministic host-classified risk categories as explicit focus labels. A
66
- fresh verifier must confirm or reject every discovered candidate. These are
67
- S1 attached ephemeral children using Effect structured concurrency. Verifiers
68
- receive the exact candidate claims and the complete bounded unit, including
69
- neighboring evidence that can falsify a locally plausible claim; they do not
70
- receive discovery reasoning.
71
- The host reconstructs publishable findings and concerns from exact confirmed
72
- candidate IDs, deduplicates byte-identical cross-pass claims in deterministic
73
- plan order, and requires every delegation declaration to be consumed by one
74
- exact planned request. Neither a discovery child nor the coordinator can publish
75
- an unsupported or invented finding. Shared `guidance` reaches both discovery
76
- and verification; `maxFindings` remains a host-enforced publication cap.
66
+ fresh verifier must confirm or reject every discovered candidate. Every pass
67
+ runs as a bounded evidence-only child dispatched directly by host code with
68
+ Effect structured concurrency there is no coordinator model and no
69
+ delegation tool, so nothing depends on a model copying the plan correctly. A
70
+ pass that fails (child fault, malformed or misdirected output) is retried
71
+ once; a pass that stays failed is reported and its unit's paths carry forward
72
+ as retryable scope. Verifiers receive the exact candidate claims and the
73
+ complete bounded unit, including neighboring evidence that can falsify a
74
+ locally plausible claim; they do not receive discovery reasoning.
75
+ The host builds publishable findings and concerns from exact confirmed
76
+ candidate IDs and deduplicates byte-identical cross-pass claims in
77
+ deterministic plan order; a discovery claim anchored outside its assigned
78
+ evidence is discarded and counted, never published and never pass-fatal. The
79
+ summary and verdict are composed deterministically from the confirmed
80
+ severities. Shared `guidance` reaches both discovery and verification;
81
+ `maxFindings` remains a host-enforced publication cap.
77
82
 
78
83
  GitHub may omit the `patch` field for large textual files as well as binary
79
84
  files. The GitHub source recovers a missing patch by reading bounded, strict
@@ -81,24 +86,29 @@ UTF-8 base/head content: additions require the head, deletions require the
81
86
  base, and other changes require both. Reviewers receive that content through
82
87
  the ordinary diff-read tool with non-anchorable `B`/`H` line labels and must
83
88
  report defects as review-body concerns. Invalid UTF-8, binary NUL content,
84
- missing sides, files beyond the per-side read bound, and complete B/H evidence
85
- beyond the model-facing render bound remain explicit coverage gaps.
89
+ missing sides, and files beyond the per-side read bound leave a path with no
90
+ reviewable textual evidence. Such paths keep input coverage incomplete and are
91
+ carried for as long as they are part of the pull request — an unreviewable
92
+ change must never authorize a green check; exclude them deliberately with
93
+ ignore globs when that is intended.
86
94
 
87
95
  ## Assurance model
88
96
 
89
97
  The public result deliberately separates two claims:
90
98
 
91
- - **Input coverage** says every required path in the selected scope was
92
- assigned every deterministic bounded evidence shard and separately names
93
- partially assigned and unassigned paths, the exact unassigned-shard count
94
- with a bounded identifier sample, undiffable paths, source truncation, or
95
- over-capacity paths. A large textual diff is split across
96
- shards and units instead of being called partial merely for exceeding one
97
- prompt chunk. Input coverage does not say the model understood the evidence.
98
- - **Review assurance** says every configured general discovery pass, required
99
- independent specialist pass, and candidate-verification batch settled
100
- exactly. It reports discovered, confirmed, rejected, and unsettled
101
- candidate counts plus failed pass IDs.
99
+ - **Input coverage** says every reviewable required path in the selected
100
+ scope was assigned every deterministic bounded evidence shard and
101
+ separately names partially assigned and over-capacity paths, the exact
102
+ unassigned-shard count with a bounded identifier sample, undiffable paths,
103
+ and source truncation. A large textual diff is split across shards and
104
+ units instead of being called partial merely for exceeding one prompt
105
+ chunk. Input coverage does not say the model understood the evidence.
106
+ - **Review assurance** says every scheduled general discovery pass, required
107
+ independent specialist pass, and candidate-verification pass settled after
108
+ at most one retry. It reports discovered, confirmed, rejected, unsettled,
109
+ and discarded (invalid-anchor) candidate counts plus failed pass IDs. A
110
+ failed pass is a reviewer-side gap: its unit's paths carry forward as
111
+ retryable scope and the next incremental run re-reviews exactly them.
102
112
 
103
113
  `settled` assurance means the configured work completed; it never means the
104
114
  review is exhaustive or the pull request is defect-free. Risk classification
@@ -126,10 +136,9 @@ discovery/specialist/verification settlement, and candidate dispositions.
126
136
  Below the summary, a collapsed **📝 Walkthrough** table carries the
127
137
  model's one-sentence per-file change summaries — walkthrough paths are
128
138
  validated like finding anchors, so entries naming files outside the changeset
129
- are dropped. Under fan-out the walkthrough is additionally host-verified
130
- against the delegation Tool events: only summaries a successfully settled
131
- child actually reported for its own unit's paths survive, so neither a child
132
- nor the coordinator can smuggle or invent entries. Non-anchored `concerns`
139
+ are dropped. Under fan-out only summaries a
140
+ successfully settled general pass actually reported for its own unit's paths
141
+ survive, so a child cannot smuggle or invent entries. Non-anchored `concerns`
133
142
  (deletion plans, rollout sequencing,
134
143
  coverage gaps, scope questions — things with no diff line to point at) render
135
144
  as severity-tagged sections; demoted findings and findings carried from
@@ -137,7 +146,8 @@ unchanged scope collapse into counted `<details>` sections.
137
146
 
138
147
  Each inline comment is headed by its severity plus an optional model-claimed
139
148
  category chip (`**[⚠️ important · security] …**`), carries a committable
140
- GitHub `suggestion` fence when the model provided one, and ends with a
149
+ GitHub `suggestion` fence when independent verification settled the model's
150
+ suggestion as committable replacement source, and ends with a
141
151
  collapsed **🤖 Prompt for AI agents** — a copy-paste instruction derived
142
152
  host-side from the validated finding, opening with a fixed preamble telling
143
153
  the receiving agent to treat the finding content as untrusted review data.
@@ -171,17 +181,28 @@ ordinary gate.
171
181
 
172
182
  ## Incremental Action reviews
173
183
 
174
- A posted Action review with complete input coverage and settled review
175
- assurance carries bounded, versioned, HMAC-authenticated continuity state:
176
- the exact PR/base/head lineage, profile and settled-scope fingerprints, and
177
- the still-unresolved findings and concerns. A later Action run validates the
178
- state and reviews the GitHub comparison from that reviewed head to the
179
- current head, not the complete base...HEAD diff. Unchanged settled scope is
180
- not sent back to the model; unchanged unresolved findings remain active;
181
- changed or reverted paths invalidate their prior findings and receive fresh
182
- discovery and verification. Non-anchored
183
- concerns are carried conservatively until a full audit because they cannot be
184
- mapped safely to one path.
184
+ Every completed Action review that can be signed carries bounded, versioned,
185
+ HMAC-authenticated continuity state: the exact PR/base/head lineage, profile
186
+ and settled-scope fingerprints, the still-unresolved findings and concerns,
187
+ any retryable unreviewed paths, and whether the run fully settled. The
188
+ baseline is monotone by design a flaky or failed pass carries exactly its
189
+ own scope forward instead of freezing the baseline and reopening everything
190
+ reviewed since (the non-converging loop of issue #131). A later Action run
191
+ validates the state and reviews the GitHub comparison from that reviewed head
192
+ to the current head plus the carried unreviewed paths, not the complete
193
+ base...HEAD diff. When that head is not a git ancestor a rebase, amend, or
194
+ force-push the host falls back to a two-dot tree comparison and reviews only
195
+ the current PR paths whose contents actually changed, plus leftovers. Unchanged
196
+ settled scope is not sent back to the model; unchanged unresolved findings
197
+ remain active; changed or reverted paths invalidate their prior findings and
198
+ receive fresh discovery and verification. An unchanged leftover from a failed
199
+ pass retries only that failed stage and keeps its stored findings — it does
200
+ not run a second general discovery.
201
+ Whole overflow files are reviewed in bounded installments across pushes
202
+ through the same carry; a single file beyond total plan capacity and
203
+ undiffable paths stay explicitly incomplete instead — an unreviewable tail
204
+ never moves behind a green check. Non-anchored concerns are carried conservatively
205
+ until a full audit because they cannot be mapped safely to one path.
185
206
 
186
207
  The state marker must be terminal, signed with the configured stable
187
208
  `PR_REVIEW_STATE_SECRET`, authored by the configured review-posting bot, and
@@ -191,17 +212,20 @@ pinned to the reviewed commit. The expected author defaults to
191
212
  App token. State lookup, authentication, schema, identity, ancestry, profile,
192
213
  and comparison checks are fail-closed for scope selection: missing, stale,
193
214
  incompatible, or truncated state/comparisons produce a visible full-diff
194
- fallback. An ancestor base advance remains incremental and adds overlapping
215
+ fallback unless a two-dot content comparison can still name the changed PR
216
+ paths. An ancestor base advance remains incremental and adds overlapping
195
217
  PR paths as affected context; a materially changed base lineage falls back to
196
- full unless the authenticated settled-scope fingerprint still matches. The
218
+ full unless the authenticated settled-scope fingerprint still matches or the
219
+ content comparison intersects the current PR path set. The
197
220
  schema field retains the compatibility name `acceptedScopeFingerprint`. That
198
221
  fingerprint hashes the ignore-filtered effective diff, patchless base/head
199
222
  evidence, PR framing, and review-shaping profile while excluding commit IDs,
200
- base ancestry, and unified-diff hunk coordinates. Re-running the same settled
201
- head or a patch-equivalent rebase skips model execution by default while
202
- preserving its stored blocking/success conclusion and posting no duplicate
203
- review comments. Missing/corrupt state and any fingerprint or profile mismatch
204
- review conservatively.
223
+ base ancestry, and unified-diff hunk coordinates. Re-running the same head or a
224
+ patch-equivalent rebase skips model execution by default ONLY when the stored
225
+ state fully settled a state carrying unreviewed scope is always retried.
226
+ The preserved skip keeps its stored blocking/success conclusion and posts no
227
+ duplicate review comments. Missing/corrupt state and any fingerprint or
228
+ profile mismatch review conservatively.
205
229
 
206
230
  After a new state-bearing Action review posts, prior marker-bearing bot reviews
207
231
  are retired by default: their bodies become collapsed, superseded history,
package/dist/action.d.mts CHANGED
@@ -1,11 +1,8 @@
1
- import { $t as ReviewMode, Ft as ReviewRetirementHost, St as PriorReviews, br as PullRequestSource, in as ReviewStateAuthenticator, wr as ChangedFile, wt as ReviewPublisher, xr as PullRequestSourceFailure, yr as PullRequestMetadata, yt as GitHubApiFailure } from "./fan-out-n-00ppWr.mjs";
1
+ import { L as PriorReviews, P as GitHubApiFailure, X as ReviewRetirementHost, cr as PullRequestSource, fr as ChangedFile, gt as ReviewMode, lr as PullRequestSourceFailure, sr as PullRequestMetadata, xt as ReviewStateAuthenticator, z as ReviewPublisher } from "./fan-out-Bi1v0VaU.mjs";
2
2
  import { EffortPosition, InvalidEffortInput, ReviewProgressReporter, ReviewProvider, ReviewRunOutcome, ReviewTargetUnresolved, RunReviewOptions } from "./index.mjs";
3
3
  import { Config, Effect, FileSystem, Schema } from "effect";
4
4
  import { BudgetExceeded } from "effect-agent";
5
5
  //#region src/action.d.ts
6
- /** Deprecated compatibility input; host-derived conclusions are unconditional. */
7
- declare const FailOnPolicy: Schema.Literals<readonly ["never", "request-changes"]>;
8
- type FailOnPolicy = typeof FailOnPolicy.Type;
9
6
  declare const ReviewCheckConclusion: Schema.Literals<readonly ["success", "blocking", "incomplete"]>;
10
7
  type ReviewCheckConclusion = typeof ReviewCheckConclusion.Type;
11
8
  declare const ReviewGateFailed_base: Schema.Class<ReviewGateFailed, Schema.TaggedStruct<"ReviewGateFailed", {
@@ -37,8 +34,6 @@ interface ResolvedActionInputs {
37
34
  readonly maxFindings: number | undefined;
38
35
  readonly maxDurationMinutes: number | undefined;
39
36
  readonly reviewMode: ReviewMode;
40
- /** Deprecated compatibility input; conclusions are always conservative. */
41
- readonly failOn: FailOnPolicy;
42
37
  readonly skipUnchanged: boolean;
43
38
  readonly retireStaleReviews: boolean;
44
39
  readonly progressComment: boolean;
@@ -57,7 +52,6 @@ declare const resolveActionInputs: () => Effect.Effect<{
57
52
  maxFindings: number | undefined;
58
53
  maxDurationMinutes: number | undefined;
59
54
  reviewMode: "final" | "incremental";
60
- failOn: "never" | "request-changes";
61
55
  skipUnchanged: boolean;
62
56
  retireStaleReviews: boolean;
63
57
  progressComment: boolean;
@@ -113,7 +107,15 @@ interface HarnessedReviewer<E, R, FingerprintE, FingerprintR> {
113
107
  }, FingerprintE, FingerprintR> | undefined;
114
108
  readonly filterFiles?: ((files: ReadonlyArray<ChangedFile>) => ReadonlyArray<ChangedFile>) | undefined;
115
109
  }
116
- /** Host-derived check conclusion; model verdict prose cannot weaken it. */
110
+ /**
111
+ * Host-derived check conclusion; model verdict prose cannot weaken it.
112
+ *
113
+ * Blocking code findings outrank machinery gaps — they are the actionable
114
+ * signal. A machinery gap (incomplete input, unsettled passes) concludes
115
+ * `incomplete` with reasons that explicitly say the failure is reviewer-side
116
+ * uncertainty carried forward for retry, never an invitation to change code.
117
+ * The flat reviewer's constant `unverified` assurance is not a gap.
118
+ */
117
119
  declare const concludeReviewOutcome: (outcome: ReviewRunOutcome) => {
118
120
  readonly conclusion: ReviewCheckConclusion;
119
121
  readonly reasons: ReadonlyArray<string>;
@@ -129,7 +131,6 @@ declare const concludeReviewOutcome: (outcome: ReviewRunOutcome) => {
129
131
  */
130
132
  declare const runReviewAction: <E, R, FingerprintE = never, FingerprintR = never>(reviewer: HarnessedReviewer<E, R, FingerprintE, FingerprintR>, options?: {
131
133
  readonly post?: boolean | undefined;
132
- readonly failOn?: FailOnPolicy | undefined;
133
134
  /** Skip model execution when the current head already has complete stored coverage. */
134
135
  readonly skipUnchanged?: boolean | undefined;
135
136
  /** Incremental by default; `final` deliberately re-reviews the full PR diff. */
@@ -165,7 +166,7 @@ declare const reviewActionProgram: Effect.Effect<{
165
166
  } | {
166
167
  _tag: "Completed";
167
168
  outcome: ReviewRunOutcome;
168
- }, import("effect-agent").AgentApprovalDenied | import("effect-agent").AgentApprovalPending | import("effect-agent").AgentChildPending | import("effect-agent").AgentInputError | import("effect-agent").AgentOutputError | import("effect-agent").AgentPolicyError | import("effect-agent").AgentToolAuthorizationDenied | import("effect/unstable/ai/AiError").AiError | import("effect-agent").BudgetAdapterError | BudgetExceeded | Config.ConfigError | import("effect-agent").ContextOverflowError | GitHubApiFailure | GuidanceFileUnreadable | InvalidEffortInput | InvalidMaxDurationInput | import("effect-agent").ModelProtocolError | import("effect/PlatformError").PlatformError | PullRequestSourceFailure | ReviewGateFailed | ReviewTargetUnresolved | Schema.SchemaError, FileSystem.FileSystem | import("effect/unstable/http/HttpClient").HttpClient>;
169
+ }, import("effect-agent").AgentApprovalDenied | import("effect-agent").AgentApprovalPending | import("effect-agent").AgentChildPending | import("effect-agent").AgentInputError | import("effect-agent").AgentOutputError | import("effect-agent").AgentPolicyError | import("effect-agent").AgentToolAuthorizationDenied | import("effect/unstable/ai/AiError").AiError | import("effect-agent").BudgetAdapterError | BudgetExceeded | Config.ConfigError | import("effect-agent").ContextOverflowError | GitHubApiFailure | GuidanceFileUnreadable | InvalidEffortInput | InvalidMaxDurationInput | import("effect-agent").ModelProtocolError | import("effect/PlatformError").PlatformError | PullRequestSourceFailure | ReviewGateFailed | ReviewTargetUnresolved | Schema.SchemaError, import("effect/Crypto").Crypto | FileSystem.FileSystem | import("effect/unstable/http/HttpClient").HttpClient>;
169
170
  /** Run the packaged action program on Node; the bundled action entrypoint. */
170
171
  declare const main: () => void;
171
172
  /** The packaged GitHub Actions surface. */
@@ -183,7 +184,6 @@ declare const PrReviewAction: {
183
184
  maxFindings: number | undefined;
184
185
  maxDurationMinutes: number | undefined;
185
186
  reviewMode: "final" | "incremental";
186
- failOn: "never" | "request-changes";
187
187
  skipUnchanged: boolean;
188
188
  retireStaleReviews: boolean;
189
189
  progressComment: boolean;
@@ -195,9 +195,9 @@ declare const PrReviewAction: {
195
195
  } | {
196
196
  _tag: "Completed";
197
197
  outcome: ReviewRunOutcome;
198
- }, import("effect-agent").AgentApprovalDenied | import("effect-agent").AgentApprovalPending | import("effect-agent").AgentChildPending | import("effect-agent").AgentInputError | import("effect-agent").AgentOutputError | import("effect-agent").AgentPolicyError | import("effect-agent").AgentToolAuthorizationDenied | import("effect/unstable/ai/AiError").AiError | import("effect-agent").BudgetAdapterError | BudgetExceeded | Config.ConfigError | import("effect-agent").ContextOverflowError | GitHubApiFailure | GuidanceFileUnreadable | InvalidEffortInput | InvalidMaxDurationInput | import("effect-agent").ModelProtocolError | import("effect/PlatformError").PlatformError | PullRequestSourceFailure | ReviewGateFailed | ReviewTargetUnresolved | Schema.SchemaError, FileSystem.FileSystem | import("effect/unstable/http/HttpClient").HttpClient>;
198
+ }, import("effect-agent").AgentApprovalDenied | import("effect-agent").AgentApprovalPending | import("effect-agent").AgentChildPending | import("effect-agent").AgentInputError | import("effect-agent").AgentOutputError | import("effect-agent").AgentPolicyError | import("effect-agent").AgentToolAuthorizationDenied | import("effect/unstable/ai/AiError").AiError | import("effect-agent").BudgetAdapterError | BudgetExceeded | Config.ConfigError | import("effect-agent").ContextOverflowError | GitHubApiFailure | GuidanceFileUnreadable | InvalidEffortInput | InvalidMaxDurationInput | import("effect-agent").ModelProtocolError | import("effect/PlatformError").PlatformError | PullRequestSourceFailure | ReviewGateFailed | ReviewTargetUnresolved | Schema.SchemaError, import("effect/Crypto").Crypto | FileSystem.FileSystem | import("effect/unstable/http/HttpClient").HttpClient>;
199
199
  readonly main: typeof main;
200
200
  };
201
201
  //#endregion
202
- export { FailOnPolicy, GuidanceFileUnreadable, HarnessedReviewer, InvalidMaxDurationInput, MAX_GUIDANCE_FILE_CHARS, PrReviewAction, ResolvedActionInputs, ReviewActionResult, ReviewCheckConclusion, ReviewGateFailed, concludeReviewOutcome, main, resolveActionInputs, resolveGuidance, reviewActionProgram, runReviewAction, writeActionOutputs, writeStepSummary };
202
+ export { GuidanceFileUnreadable, HarnessedReviewer, InvalidMaxDurationInput, MAX_GUIDANCE_FILE_CHARS, PrReviewAction, ResolvedActionInputs, ReviewActionResult, ReviewCheckConclusion, ReviewGateFailed, concludeReviewOutcome, main, resolveActionInputs, resolveGuidance, reviewActionProgram, runReviewAction, writeActionOutputs, writeStepSummary };
203
203
  //# sourceMappingURL=action.d.mts.map
package/dist/action.mjs CHANGED
@@ -1,13 +1,11 @@
1
- import { $n as normalizeRepoRelativePath, A as ReviewStateAuthenticator, B as selectedPullRequestSourceLayer, G as webCryptoReviewStateAuthenticatorLayer, T as ReviewHeadComparison, U as unavailableReviewStateAuthenticatorLayer, W as validateReviewState, Xn as PullRequestSource, a as PriorReviews, w as ReviewExecutionContext, x as retireStaleReviews, z as selectReviewRange } from "./github-BgtP7Rdv.mjs";
2
- import { C as PrReview, O as fanOutReviewBudgetLimits, U as parseEffortPosition, V as InvalidEffortInput, a as anthropicClientLayer, c as makeOpenAiReviewModel, f as gitHubReviewLayers, g as ReviewProgressReporter, k as reviewBudgetLimits, l as openAiClientLayer, m as resolveReviewTarget, n as DEFAULT_PROVIDER, o as describeReviewModel, p as readGitHubEvent, s as makeAnthropicReviewModel } from "./providers-BguZK4B_.mjs";
1
+ import { D as ReviewHeadComparison, E as ReviewExecutionContext, H as isLineageAncestor, J as validateReviewState, M as ReviewStateAuthenticator, U as selectReviewRange, W as selectedPullRequestSourceLayer, Xn as normalizeRepoRelativePath, Y as webCryptoReviewStateAuthenticatorLayer, a as PriorReviews, q as unavailableReviewStateAuthenticatorLayer, qn as PullRequestSource, x as retireStaleReviews } from "./github-C6jrBLA2.mjs";
2
+ import { A as reviewBudgetLimits, C as PrReview, H as InvalidEffortInput, W as parseEffortPosition, a as anthropicClientLayer, c as makeOpenAiReviewModel, f as gitHubReviewLayers, g as ReviewProgressReporter, k as fanOutReviewBudgetLimits, l as openAiClientLayer, m as resolveReviewTarget, n as DEFAULT_PROVIDER, o as describeReviewModel, p as readGitHubEvent, s as makeAnthropicReviewModel } from "./providers-2Jao2ZAX.mjs";
3
3
  import { n as compactReviewLoggingLayer } from "./logging-Q4j0oub-.mjs";
4
4
  import { Config, Console, Effect, FileSystem, Layer, Option, Redacted, Schema } from "effect";
5
5
  import { BudgetExceeded, UsageBudgetLimits } from "effect-agent";
6
6
  import { FetchHttpClient } from "effect/unstable/http";
7
7
  import { NodeRuntime, NodeServices } from "@effect/platform-node";
8
8
  //#region src/action.ts
9
- /** Deprecated compatibility input; host-derived conclusions are unconditional. */
10
- const FailOnPolicy = Schema.Literals(["never", "request-changes"]);
11
9
  const ReviewCheckConclusion = Schema.Literals([
12
10
  "success",
13
11
  "blocking",
@@ -48,7 +46,6 @@ const resolveActionInputs = Effect.fn("resolveActionInputs")(function* () {
48
46
  const ignoreRaw = yield* Config.string("PR_REVIEW_IGNORE").pipe(Config.withDefault(""));
49
47
  const maxFindings = yield* Config.option(Config.int("PR_REVIEW_MAX_FINDINGS"));
50
48
  const reviewMode = yield* Config.literals(["incremental", "final"], "PR_REVIEW_MODE").pipe(Config.withDefault("incremental"));
51
- const failOn = yield* Config.literals(["never", "request-changes"], "PR_REVIEW_FAIL_ON").pipe(Config.withDefault("never"));
52
49
  const skipUnchanged = yield* Config.boolean("PR_REVIEW_SKIP_UNCHANGED").pipe(Config.withDefault(true));
53
50
  const retireStaleReviews = yield* Config.boolean("PR_REVIEW_RETIRE_STALE_REVIEWS").pipe(Config.withDefault(true));
54
51
  const progressComment = yield* Config.boolean("PR_REVIEW_PROGRESS_COMMENT").pipe(Config.withDefault(true));
@@ -65,7 +62,6 @@ const resolveActionInputs = Effect.fn("resolveActionInputs")(function* () {
65
62
  maxFindings: Option.getOrUndefined(maxFindings),
66
63
  maxDurationMinutes,
67
64
  reviewMode,
68
- failOn,
69
65
  skipUnchanged,
70
66
  retireStaleReviews,
71
67
  progressComment
@@ -137,6 +133,7 @@ const outcomeOutputs = (outcome, conclusion) => [
137
133
  ["inline-comments", String(outcome.plan.comments.length)],
138
134
  ["demoted-findings", String(outcome.plan.demoted.length)],
139
135
  ["concerns", String(outcome.review.concerns?.length ?? 0)],
136
+ ["unreviewed-paths", String(outcome.unreviewedPaths.length)],
140
137
  ...outcome.usage === void 0 ? [] : [["input-tokens", String(outcome.usage.inputTokens)], ["output-tokens", String(outcome.usage.outputTokens)]],
141
138
  ["review-url", outcome.published?.url ?? ""]
142
139
  ];
@@ -147,8 +144,9 @@ const outcomeSummary = (outcome, modelLabel, conclusion) => [
147
144
  `- Input coverage: **${outcome.inputCoverage.status}** · scope: ${outcome.reviewMode ?? "full"}`,
148
145
  `- Review assurance: **${outcome.assurance.status}** · general discovery ${outcome.assurance.completedGeneralDiscoveryPasses}/${outcome.assurance.requiredGeneralDiscoveryPasses} · specialist ${outcome.assurance.completedSpecialistPasses}/${outcome.assurance.requiredSpecialistPasses} · verification ${outcome.assurance.completedVerificationPasses}/${outcome.assurance.requiredVerificationPasses}`,
149
146
  `- Inline comments: ${outcome.plan.comments.length} · demoted findings: ${outcome.plan.demoted.length} · concerns: ${outcome.review.concerns?.length ?? 0}`,
147
+ ...outcome.unreviewedPaths.length === 0 ? [] : [`- Carried forward: ${outcome.unreviewedPaths.length} unreviewed path(s) retried automatically on the next run (reviewer-side gap, not a code defect)`],
150
148
  ...modelLabel === void 0 ? [] : [`- Model: \`${modelLabel}\``],
151
- ...outcome.usage === void 0 ? [] : [`- Tokens: ${outcome.usage.inputTokens} in / ${outcome.usage.outputTokens} out${outcome.usageScope === "coordinator" ? " (coordinator)" : ""}`],
149
+ ...outcome.usage === void 0 ? [] : [`- Tokens: ${outcome.usage.inputTokens} in / ${outcome.usage.outputTokens} out`],
152
150
  ...outcome.published === void 0 ? ["- Dry run: nothing posted"] : [`- Posted: ${outcome.published.url}`]
153
151
  ];
154
152
  const skip = (reason) => Effect.gen(function* () {
@@ -168,26 +166,37 @@ const resolveRunUrl = Effect.fn("resolveRunUrl")(function* () {
168
166
  return `${yield* Config.string("GITHUB_SERVER_URL").pipe(Config.withDefault("https://github.com"))}/${repository}/actions/runs/${runId}`;
169
167
  });
170
168
  const blockingReasons = (input) => [...input.findings.filter((finding) => finding.severity === "blocking").map((finding) => `blocking finding: ${finding.title}`), ...input.concerns.filter((concern) => concern.severity === "blocking").map((concern) => `blocking concern: ${concern.title}`)];
171
- /** Host-derived check conclusion; model verdict prose cannot weaken it. */
169
+ /**
170
+ * Host-derived check conclusion; model verdict prose cannot weaken it.
171
+ *
172
+ * Blocking code findings outrank machinery gaps — they are the actionable
173
+ * signal. A machinery gap (incomplete input, unsettled passes) concludes
174
+ * `incomplete` with reasons that explicitly say the failure is reviewer-side
175
+ * uncertainty carried forward for retry, never an invitation to change code.
176
+ * The flat reviewer's constant `unverified` assurance is not a gap.
177
+ */
172
178
  const concludeReviewOutcome = (outcome) => {
173
- if (outcome.inputCoverage.status === "incomplete") return {
174
- conclusion: "incomplete",
175
- reasons: outcome.inputCoverage.reasons
176
- };
177
- if (outcome.assurance.status !== "settled") return {
178
- conclusion: "incomplete",
179
- reasons: outcome.assurance.reasons.length > 0 ? outcome.assurance.reasons : ["configured discovery and verification work did not settle"]
180
- };
181
- const reasons = blockingReasons({
179
+ const machinery = [];
180
+ if (outcome.inputCoverage.status === "incomplete" || outcome.assurance.status === "incomplete") {
181
+ machinery.push(outcome.unreviewedPaths.length > 0 ? `review infrastructure did not settle — a reviewer-side gap, not a code defect; ${outcome.unreviewedPaths.length} path(s) are carried forward and retried automatically on the next run` : "review infrastructure did not settle — a reviewer-side gap, not a code defect");
182
+ if (outcome.inputCoverage.status === "incomplete") machinery.push(...outcome.inputCoverage.reasons);
183
+ if (outcome.assurance.status === "incomplete") machinery.push(...outcome.assurance.reasons);
184
+ }
185
+ const blocking = blockingReasons({
182
186
  findings: outcome.activeFindings,
183
187
  concerns: outcome.activeConcerns
184
188
  });
185
- return reasons.length > 0 ? {
189
+ if (blocking.length > 0) return {
186
190
  conclusion: "blocking",
187
- reasons
188
- } : {
191
+ reasons: [...blocking, ...machinery]
192
+ };
193
+ if (machinery.length > 0) return {
194
+ conclusion: "incomplete",
195
+ reasons: machinery
196
+ };
197
+ return {
189
198
  conclusion: "success",
190
- reasons
199
+ reasons: []
191
200
  };
192
201
  };
193
202
  const concludeReviewState = (state) => {
@@ -274,7 +283,7 @@ const runReviewAction = (reviewer, options = {}) => Effect.gen(function* () {
274
283
  failure: void 0
275
284
  })
276
285
  }));
277
- const equivalentPatchState = (options.reviewMode ?? "incremental") === "incremental" && options.skipUnchanged !== false && recovered.state !== void 0 && currentFingerprint !== void 0 && validateReviewState(recovered.state, metadata, profileFingerprint) === void 0 && recovered.state.acceptedScopeFingerprint === currentFingerprint ? recovered.state : void 0;
286
+ const equivalentPatchState = (options.reviewMode ?? "incremental") === "incremental" && options.skipUnchanged !== false && recovered.state !== void 0 && recovered.state.settled && currentFingerprint !== void 0 && validateReviewState(recovered.state, metadata, profileFingerprint) === void 0 && recovered.state.acceptedScopeFingerprint === currentFingerprint ? recovered.state : void 0;
278
287
  if (equivalentPatchState !== void 0) return yield* skipCoveredReview({
279
288
  repository: target.repository,
280
289
  pullRequestNumber: target.number,
@@ -284,6 +293,7 @@ const runReviewAction = (reviewer, options = {}) => Effect.gen(function* () {
284
293
  });
285
294
  let comparison;
286
295
  let baseComparison;
296
+ let contentComparison;
287
297
  if ((options.reviewMode ?? "incremental") === "incremental" && recovered.state !== void 0) {
288
298
  if (recovered.state.reviewedHeadSha === metadata.headSha) comparison = ReviewHeadComparison.make({
289
299
  status: "identical",
@@ -307,6 +317,13 @@ const runReviewAction = (reviewer, options = {}) => Effect.gen(function* () {
307
317
  files: reviewer.filterFiles(baseComparison.files)
308
318
  });
309
319
  }
320
+ if (recovered.state.reviewedHeadSha !== metadata.headSha && (comparison === void 0 || !isLineageAncestor(comparison, recovered.state, metadata.headSha))) {
321
+ contentComparison = yield* history.compareTrees(recovered.state.reviewedHeadSha, metadata.headSha).pipe(Effect.orElseSucceed(() => void 0));
322
+ if (contentComparison !== void 0 && reviewer.filterFiles !== void 0) contentComparison = ReviewHeadComparison.make({
323
+ ...contentComparison,
324
+ files: reviewer.filterFiles(contentComparison.files)
325
+ });
326
+ }
310
327
  }
311
328
  selection = {
312
329
  ...selectReviewRange({
@@ -317,11 +334,12 @@ const runReviewAction = (reviewer, options = {}) => Effect.gen(function* () {
317
334
  priorState: recovered.state,
318
335
  comparison,
319
336
  baseComparison,
337
+ contentComparison,
320
338
  lookupFailure: recovered.failure
321
339
  }),
322
340
  stateAuthenticator
323
341
  };
324
- if (options.skipUnchanged !== false && selection.mode === "incremental" && selection.files.length === 0 && selection.priorState !== void 0) return yield* skipCoveredReview({
342
+ if (options.skipUnchanged !== false && selection.mode === "incremental" && selection.files.length === 0 && selection.priorState !== void 0 && selection.priorState.settled) return yield* skipCoveredReview({
325
343
  repository: target.repository,
326
344
  pullRequestNumber: target.number,
327
345
  reason: "no changed review scope since the last settled review head",
@@ -418,7 +436,6 @@ const reviewActionProgram = Effect.gen(function* () {
418
436
  };
419
437
  const harness = {
420
438
  post: inputs.post,
421
- failOn: inputs.failOn,
422
439
  skipUnchanged: inputs.skipUnchanged,
423
440
  reviewMode: inputs.reviewMode,
424
441
  retireStaleReviews: inputs.retireStaleReviews,
@@ -456,6 +473,6 @@ const PrReviewAction = {
456
473
  main
457
474
  };
458
475
  //#endregion
459
- export { FailOnPolicy, GuidanceFileUnreadable, InvalidMaxDurationInput, MAX_GUIDANCE_FILE_CHARS, PrReviewAction, ReviewCheckConclusion, ReviewGateFailed, concludeReviewOutcome, main, resolveActionInputs, resolveGuidance, reviewActionProgram, runReviewAction, writeActionOutputs, writeStepSummary };
476
+ export { GuidanceFileUnreadable, InvalidMaxDurationInput, MAX_GUIDANCE_FILE_CHARS, PrReviewAction, ReviewCheckConclusion, ReviewGateFailed, concludeReviewOutcome, main, resolveActionInputs, resolveGuidance, reviewActionProgram, runReviewAction, writeActionOutputs, writeStepSummary };
460
477
 
461
478
  //# sourceMappingURL=action.mjs.map