@effect-agent/pr-review 0.1.0-beta.21 → 0.1.0-beta.23

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/src/action.ts CHANGED
@@ -51,10 +51,6 @@ import {
51
51
  // nothing.
52
52
  // ---------------------------------------------------------------------------
53
53
 
54
- /** Deprecated compatibility input; host-derived conclusions are unconditional. */
55
- export const FailOnPolicy = Schema.Literals(["never", "request-changes"]);
56
- export type FailOnPolicy = typeof FailOnPolicy.Type;
57
-
58
54
  export const ReviewCheckConclusion = Schema.Literals(["success", "blocking", "incomplete"]);
59
55
  export type ReviewCheckConclusion = typeof ReviewCheckConclusion.Type;
60
56
 
@@ -96,8 +92,6 @@ export interface ResolvedActionInputs {
96
92
  readonly maxFindings: number | undefined;
97
93
  readonly maxDurationMinutes: number | undefined;
98
94
  readonly reviewMode: ReviewMode;
99
- /** Deprecated compatibility input; conclusions are always conservative. */
100
- readonly failOn: FailOnPolicy;
101
95
  readonly skipUnchanged: boolean;
102
96
  readonly retireStaleReviews: boolean;
103
97
  readonly progressComment: boolean;
@@ -135,9 +129,6 @@ export const resolveActionInputs = Effect.fn("resolveActionInputs")(function* ()
135
129
  const reviewMode = yield* Config.literals(["incremental", "final"], "PR_REVIEW_MODE").pipe(
136
130
  Config.withDefault<ReviewMode>("incremental"),
137
131
  );
138
- const failOn = yield* Config.literals(["never", "request-changes"], "PR_REVIEW_FAIL_ON").pipe(
139
- Config.withDefault<FailOnPolicy>("never"),
140
- );
141
132
  const skipUnchanged = yield* Config.boolean("PR_REVIEW_SKIP_UNCHANGED").pipe(
142
133
  Config.withDefault(true),
143
134
  );
@@ -163,7 +154,6 @@ export const resolveActionInputs = Effect.fn("resolveActionInputs")(function* ()
163
154
  maxFindings: Option.getOrUndefined(maxFindings),
164
155
  maxDurationMinutes,
165
156
  reviewMode,
166
- failOn,
167
157
  skipUnchanged,
168
158
  retireStaleReviews,
169
159
  progressComment,
@@ -276,6 +266,7 @@ const outcomeOutputs = (
276
266
  ["inline-comments", String(outcome.plan.comments.length)],
277
267
  ["demoted-findings", String(outcome.plan.demoted.length)],
278
268
  ["concerns", String(outcome.review.concerns?.length ?? 0)],
269
+ ["unreviewed-paths", String(outcome.unreviewedPaths.length)],
279
270
  ...(outcome.usage === undefined
280
271
  ? []
281
272
  : ([
@@ -296,14 +287,15 @@ const outcomeSummary = (
296
287
  `- Input coverage: **${outcome.inputCoverage.status}** · scope: ${outcome.reviewMode ?? "full"}`,
297
288
  `- 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}`,
298
289
  `- Inline comments: ${outcome.plan.comments.length} · demoted findings: ${outcome.plan.demoted.length} · concerns: ${outcome.review.concerns?.length ?? 0}`,
299
- ...(modelLabel === undefined ? [] : [`- Model: \`${modelLabel}\``]),
300
- ...(outcome.usage === undefined
290
+ ...(outcome.unreviewedPaths.length === 0
301
291
  ? []
302
292
  : [
303
- `- Tokens: ${outcome.usage.inputTokens} in / ${outcome.usage.outputTokens} out${
304
- outcome.usageScope === "coordinator" ? " (coordinator)" : ""
305
- }`,
293
+ `- Carried forward: ${outcome.unreviewedPaths.length} unreviewed path(s) retried automatically on the next run (reviewer-side gap, not a code defect)`,
306
294
  ]),
295
+ ...(modelLabel === undefined ? [] : [`- Model: \`${modelLabel}\``]),
296
+ ...(outcome.usage === undefined
297
+ ? []
298
+ : [`- Tokens: ${outcome.usage.inputTokens} in / ${outcome.usage.outputTokens} out`]),
307
299
  ...(outcome.published === undefined
308
300
  ? ["- Dry run: nothing posted"]
309
301
  : [`- Posted: ${outcome.published.url}`]),
@@ -376,32 +368,44 @@ const blockingReasons = (input: {
376
368
  .map((concern) => `blocking concern: ${concern.title}`),
377
369
  ];
378
370
 
379
- /** Host-derived check conclusion; model verdict prose cannot weaken it. */
371
+ /**
372
+ * Host-derived check conclusion; model verdict prose cannot weaken it.
373
+ *
374
+ * Blocking code findings outrank machinery gaps — they are the actionable
375
+ * signal. A machinery gap (incomplete input, unsettled passes) concludes
376
+ * `incomplete` with reasons that explicitly say the failure is reviewer-side
377
+ * uncertainty carried forward for retry, never an invitation to change code.
378
+ * The flat reviewer's constant `unverified` assurance is not a gap.
379
+ */
380
380
  export const concludeReviewOutcome = (
381
381
  outcome: ReviewRunOutcome,
382
382
  ): {
383
383
  readonly conclusion: ReviewCheckConclusion;
384
384
  readonly reasons: ReadonlyArray<string>;
385
385
  } => {
386
- if (outcome.inputCoverage.status === "incomplete") {
387
- return { conclusion: "incomplete", reasons: outcome.inputCoverage.reasons };
388
- }
389
- if (outcome.assurance.status !== "settled") {
390
- return {
391
- conclusion: "incomplete",
392
- reasons:
393
- outcome.assurance.reasons.length > 0
394
- ? outcome.assurance.reasons
395
- : ["configured discovery and verification work did not settle"],
396
- };
386
+ const machinery: Array<string> = [];
387
+ if (outcome.inputCoverage.status === "incomplete" || outcome.assurance.status === "incomplete") {
388
+ machinery.push(
389
+ outcome.unreviewedPaths.length > 0
390
+ ? `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`
391
+ : "review infrastructure did not settle — a reviewer-side gap, not a code defect",
392
+ );
393
+ if (outcome.inputCoverage.status === "incomplete") {
394
+ machinery.push(...outcome.inputCoverage.reasons);
395
+ }
396
+ if (outcome.assurance.status === "incomplete") {
397
+ machinery.push(...outcome.assurance.reasons);
398
+ }
397
399
  }
398
- const reasons = blockingReasons({
400
+ const blocking = blockingReasons({
399
401
  findings: outcome.activeFindings,
400
402
  concerns: outcome.activeConcerns,
401
403
  });
402
- return reasons.length > 0
403
- ? { conclusion: "blocking", reasons }
404
- : { conclusion: "success", reasons };
404
+ if (blocking.length > 0) {
405
+ return { conclusion: "blocking", reasons: [...blocking, ...machinery] };
406
+ }
407
+ if (machinery.length > 0) return { conclusion: "incomplete", reasons: machinery };
408
+ return { conclusion: "success", reasons: [] };
405
409
  };
406
410
 
407
411
  const concludeReviewState = (state: ReviewState) => {
@@ -462,7 +466,6 @@ export const runReviewAction = <E, R, FingerprintE = never, FingerprintR = never
462
466
  reviewer: HarnessedReviewer<E, R, FingerprintE, FingerprintR>,
463
467
  options: {
464
468
  readonly post?: boolean | undefined;
465
- readonly failOn?: FailOnPolicy | undefined;
466
469
  /** Skip model execution when the current head already has complete stored coverage. */
467
470
  readonly skipUnchanged?: boolean | undefined;
468
471
  /** Incremental by default; `final` deliberately re-reviews the full PR diff. */
@@ -533,6 +536,9 @@ export const runReviewAction = <E, R, FingerprintE = never, FingerprintR = never
533
536
  (options.reviewMode ?? "incremental") === "incremental" &&
534
537
  options.skipUnchanged !== false &&
535
538
  recovered.state !== undefined &&
539
+ // Only a fully settled run may be skipped over: an unsettled state
540
+ // carries retryable scope the next run must actually retry.
541
+ recovered.state.settled &&
536
542
  currentFingerprint !== undefined &&
537
543
  validateReviewState(recovered.state, metadata, profileFingerprint) === undefined &&
538
544
  recovered.state.acceptedScopeFingerprint === currentFingerprint
@@ -605,7 +611,8 @@ export const runReviewAction = <E, R, FingerprintE = never, FingerprintR = never
605
611
  options.skipUnchanged !== false &&
606
612
  selection.mode === "incremental" &&
607
613
  selection.files.length === 0 &&
608
- selection.priorState !== undefined
614
+ selection.priorState !== undefined &&
615
+ selection.priorState.settled
609
616
  ) {
610
617
  const reason = "no changed review scope since the last settled review head";
611
618
  return yield* skipCoveredReview({
@@ -747,7 +754,6 @@ export const reviewActionProgram = Effect.gen(function* () {
747
754
  };
748
755
  const harness = {
749
756
  post: inputs.post,
750
- failOn: inputs.failOn,
751
757
  skipUnchanged: inputs.skipUnchanged,
752
758
  reviewMode: inputs.reviewMode,
753
759
  retireStaleReviews: inputs.retireStaleReviews,
@@ -23,7 +23,6 @@ const INPUT_TO_ENV: ReadonlyArray<readonly [input: string, env: string]> = [
23
23
  ["INPUT_IGNORE", "PR_REVIEW_IGNORE"],
24
24
  ["INPUT_MAX-FINDINGS", "PR_REVIEW_MAX_FINDINGS"],
25
25
  ["INPUT_REVIEW-MODE", "PR_REVIEW_MODE"],
26
- ["INPUT_FAIL-ON", "PR_REVIEW_FAIL_ON"],
27
26
  ["INPUT_SKIP-UNCHANGED", "PR_REVIEW_SKIP_UNCHANGED"],
28
27
  ["INPUT_RETIRE-STALE-REVIEWS", "PR_REVIEW_RETIRE_STALE_REVIEWS"],
29
28
  ["INPUT_PROGRESS-COMMENT", "PR_REVIEW_PROGRESS_COMMENT"],