@mgiles/perk 3.2.0 → 3.3.0

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 (202) hide show
  1. package/README.md +5 -0
  2. package/extension/authoring/gist/draft.ts +198 -0
  3. package/extension/authoring/gist/prose.ts +46 -0
  4. package/extension/authoring/gist/review.ts +133 -0
  5. package/extension/authoring/gist/save.ts +118 -0
  6. package/extension/authoring/objective/draft.ts +345 -0
  7. package/extension/{factories/objectiveDreamReport.ts → authoring/objective/dreamReportGate.ts} +74 -131
  8. package/extension/authoring/objective/planning.ts +124 -0
  9. package/extension/authoring/objective/prose.ts +103 -0
  10. package/extension/authoring/objective/review.ts +128 -0
  11. package/extension/authoring/objective/save.ts +224 -0
  12. package/extension/authoring/plan/draft.ts +84 -0
  13. package/extension/authoring/plan/prose.ts +41 -0
  14. package/extension/authoring/plan/review.ts +269 -0
  15. package/extension/authoring/plan/save.ts +256 -0
  16. package/extension/authoring/plan/source.ts +82 -0
  17. package/extension/authoring/refinement/context.ts +468 -0
  18. package/extension/authoring/refinement/draft.ts +261 -0
  19. package/extension/authoring/refinement/prose.ts +79 -0
  20. package/extension/authoring/refinement/review.ts +111 -0
  21. package/extension/authoring/refinement/save.ts +119 -0
  22. package/extension/authoring/review/approvalGate.ts +34 -0
  23. package/extension/authoring/review/draftContext.ts +68 -0
  24. package/extension/codeReview/automated.ts +352 -0
  25. package/extension/codeReview/submission.ts +229 -0
  26. package/extension/delivery/address.ts +295 -0
  27. package/extension/delivery/ci.ts +355 -0
  28. package/extension/delivery/commitCompact.ts +93 -0
  29. package/extension/delivery/conflictResolution.ts +247 -0
  30. package/extension/delivery/ready.ts +193 -0
  31. package/extension/delivery/stackConflict.ts +361 -0
  32. package/extension/delivery/stackObjective.ts +16 -0
  33. package/extension/delivery/stackReconcile.ts +165 -0
  34. package/extension/delivery/submit.ts +171 -0
  35. package/extension/index.ts +365 -380
  36. package/extension/learning/analystWave.ts +324 -0
  37. package/extension/learning/audit.ts +667 -0
  38. package/extension/learning/capture.ts +92 -0
  39. package/extension/learning/containment.ts +104 -0
  40. package/extension/{waves/dreamWave.ts → learning/dream.ts} +112 -94
  41. package/extension/learning/dreamAnalysis.ts +435 -0
  42. package/extension/{waves/dreamReducerWave.ts → learning/dreamReducer.ts} +46 -41
  43. package/extension/{waves → learning}/dreamReport.ts +35 -31
  44. package/extension/learning/harvest.ts +491 -0
  45. package/extension/learning/prose.ts +66 -0
  46. package/extension/learning/routing.ts +79 -0
  47. package/extension/pi/v1/bashScanTimeout.ts +64 -0
  48. package/extension/{doors/prReview.ts → pi/v1/codeReview/automated.ts} +215 -311
  49. package/extension/{doors/prReviewBrowser.ts → pi/v1/codeReview/browser.ts} +53 -33
  50. package/extension/{doors/hunkHandoff.ts → pi/v1/codeReview/checkout.ts} +12 -8
  51. package/extension/{doors/reviewWaveTools.ts → pi/v1/codeReview/reviewWave.ts} +146 -114
  52. package/extension/{doors/stackReviewBrowser.ts → pi/v1/codeReview/stack.ts} +62 -29
  53. package/extension/pi/v1/codeReview/submit.ts +354 -0
  54. package/extension/{doors/prReviewTerminal.ts → pi/v1/codeReview/terminal.ts} +32 -27
  55. package/extension/pi/v1/contextEvidence.ts +80 -0
  56. package/extension/pi/v1/contextInjection.ts +207 -0
  57. package/extension/{doors → pi/v1/delivery}/address.ts +154 -267
  58. package/extension/pi/v1/delivery/ci.ts +570 -0
  59. package/extension/pi/v1/delivery/commitCompact.ts +201 -0
  60. package/extension/pi/v1/delivery/conflictResolverEngine.ts +425 -0
  61. package/extension/{doors → pi/v1/delivery}/land.ts +123 -61
  62. package/extension/pi/v1/delivery/ready.ts +322 -0
  63. package/extension/pi/v1/delivery/stackConflictResolver.ts +172 -0
  64. package/extension/pi/v1/delivery/stackDrive.ts +120 -0
  65. package/extension/pi/v1/delivery/stackLand.ts +223 -0
  66. package/extension/pi/v1/delivery/stackRecover.ts +265 -0
  67. package/extension/pi/v1/delivery/stackStatus.ts +237 -0
  68. package/extension/pi/v1/delivery/stackSync.ts +658 -0
  69. package/extension/pi/v1/delivery/submit.ts +389 -0
  70. package/extension/pi/v1/delivery/submitConflict.ts +186 -0
  71. package/extension/pi/v1/draftReview.ts +431 -0
  72. package/extension/{doors → pi/v1}/draftReviewWaveTools.ts +141 -151
  73. package/extension/pi/v1/gist.ts +794 -0
  74. package/extension/pi/v1/learning/audit.ts +186 -0
  75. package/extension/pi/v1/learning/dream.ts +207 -0
  76. package/extension/{doors/learnFactory.ts → pi/v1/learning/factory.ts} +18 -65
  77. package/extension/{doors/harvestWaveTools.ts → pi/v1/learning/harvest.ts} +46 -100
  78. package/extension/pi/v1/learning/learn.ts +585 -0
  79. package/extension/pi/v1/lifecycleGates.ts +127 -0
  80. package/extension/{factories → pi/v1}/objective.ts +53 -33
  81. package/extension/pi/v1/objectiveAuthoring.ts +672 -0
  82. package/extension/pi/v1/objectiveDreamGate.ts +160 -0
  83. package/extension/{factories/objectivePlan.ts → pi/v1/objectivePlanning.ts} +328 -533
  84. package/extension/pi/v1/objectiveRefinement.ts +1320 -0
  85. package/extension/pi/v1/objectiveReview.ts +451 -0
  86. package/extension/{doors → pi/v1}/objectiveReviewBrowser.ts +259 -172
  87. package/extension/pi/v1/plan.ts +812 -0
  88. package/extension/pi/v1/planReview.ts +820 -0
  89. package/extension/{doors → pi/v1}/planReviewBrowser.ts +228 -152
  90. package/extension/{doors/annotationPush.ts → pi/v1/providers/annotations.ts} +158 -89
  91. package/extension/pi/v1/providers/plannotator.ts +487 -0
  92. package/extension/{doors → pi/v1/providers}/plannotatorHandoff.ts +73 -27
  93. package/extension/pi/v1/providers/selection.ts +43 -0
  94. package/extension/{adapters/planAdapterTombell.ts → pi/v1/providers/tombell.ts} +43 -72
  95. package/extension/pi/v1/review.ts +538 -0
  96. package/extension/pi/v1/reviewOutcome.ts +9 -0
  97. package/extension/pi/v1/scoutWave.ts +318 -0
  98. package/extension/{doors → pi/v1}/selfcheck.ts +4 -4
  99. package/extension/session/branchWorkflowSession.ts +60 -0
  100. package/extension/session/lifecycle.ts +644 -0
  101. package/extension/session/lifecycleGates.ts +64 -0
  102. package/extension/session/saveDestination.ts +87 -0
  103. package/extension/session/workflowSession.ts +971 -0
  104. package/extension/substrate/agentScratch.ts +27 -54
  105. package/extension/substrate/bashScanTimeout.ts +181 -0
  106. package/extension/substrate/bindingDelivery.ts +38 -30
  107. package/extension/substrate/bindings.ts +4 -5
  108. package/extension/substrate/cache.ts +64 -12
  109. package/extension/substrate/childRestrictions.ts +39 -0
  110. package/extension/substrate/coldDoor.ts +17 -1
  111. package/extension/substrate/config.ts +157 -21
  112. package/extension/substrate/git.ts +88 -6
  113. package/extension/substrate/modelVisible.ts +53 -0
  114. package/extension/substrate/prompts.ts +22 -0
  115. package/extension/substrate/registry.ts +2 -0
  116. package/extension/substrate/resolverLease.ts +5 -4
  117. package/extension/substrate/sessionData.ts +85 -152
  118. package/extension/substrate/toolGating.ts +263 -84
  119. package/extension/substrate/unifiedDiff.ts +1 -1
  120. package/extension/substrate/workflowState.ts +178 -163
  121. package/extension/substrate/worktreeResolverLock.ts +261 -0
  122. package/extension/surfaces/surfaces.ts +79 -27
  123. package/extension/waves/adversarialReviewWave.ts +87 -46
  124. package/extension/waves/blockedReports.ts +59 -0
  125. package/extension/waves/draftReviewWave.ts +42 -42
  126. package/extension/waves/laneIdentity.ts +77 -0
  127. package/extension/waves/objectiveExplorerWave.ts +24 -24
  128. package/extension/waves/prReviewWave.ts +89 -77
  129. package/extension/waves/reportWave.ts +438 -578
  130. package/extension/waves/reviewClassifierWave.ts +22 -22
  131. package/extension/waves/rpcAdapter.ts +100 -15
  132. package/extension/waves/scoutWave.ts +192 -0
  133. package/extension/waves/transport.ts +480 -0
  134. package/extension/worker/sdkAdapter.ts +494 -0
  135. package/extension/worker/stageExecution.ts +679 -0
  136. package/extension/workerMain.ts +18 -19
  137. package/package.json +6 -4
  138. package/prompts/_fixtures/live.yaml +43 -18
  139. package/prompts/contexts/adapters/plannotator-gist.md +6 -0
  140. package/prompts/contexts/adapters/plannotator-objective.md +6 -0
  141. package/prompts/contexts/adapters/plannotator-plan.md +8 -1
  142. package/prompts/contexts/adapters/plannotator-refinement.md +22 -0
  143. package/prompts/contexts/objective-refinement.md +17 -0
  144. package/prompts/contexts/read-only.md +1 -1
  145. package/prompts/stages/conflict-resolution-continuation.md +9 -6
  146. package/prompts/stages/conflict-resolution.md +4 -4
  147. package/prompts/stages/objective-plan/guidance.md +2 -2
  148. package/prompts/stages/objective-plan/seed.md +9 -1
  149. package/prompts/stages/objective-reconcile-ready.md +1 -1
  150. package/prompts/stages/objective-reconcile.md +1 -1
  151. package/prompts/stages/objective-refine/seed.md +18 -0
  152. package/prompts/stages/objective-review-browser.md +4 -4
  153. package/prompts/stages/objective-sync.md +1 -1
  154. package/prompts/stages/plan-review-browser.md +4 -4
  155. package/prompts/stages/pr-review-browser/active.md +3 -4
  156. package/prompts/stages/pr-review-browser/foreign.md +3 -4
  157. package/prompts/stages/pr-review-terminal/active.md +3 -3
  158. package/prompts/stages/pr-review-terminal/foreign.md +3 -3
  159. package/prompts/stages/pr-review.md +3 -3
  160. package/prompts/stages/stack-review-browser/stack.md +5 -6
  161. package/shared/README.md +8 -0
  162. package/shared/bindings.yaml +3 -3
  163. package/shared/contracts.md +2601 -506
  164. package/shared/fixtures/issues-table.json +130 -0
  165. package/shared/registry.yaml +13 -0
  166. package/shared/schemas/outputs/objective-node-engagement.schema.json +318 -0
  167. package/shared/schemas/outputs/objective-stack-status.schema.json +6 -1
  168. package/shared/schemas/outputs/pr-review-context.schema.json +54 -9
  169. package/shared/schemas/outputs/pr-review-stack-context.schema.json +196 -0
  170. package/extension/adapters/planAdapterPlannotator.ts +0 -362
  171. package/extension/doors/auditWaveTools.ts +0 -352
  172. package/extension/doors/ciExecutor.ts +0 -756
  173. package/extension/doors/commitCompact.ts +0 -251
  174. package/extension/doors/dreamWaveTools.ts +0 -489
  175. package/extension/doors/learn.ts +0 -668
  176. package/extension/doors/lifecycleGates.ts +0 -207
  177. package/extension/doors/objectiveStack.ts +0 -1543
  178. package/extension/doors/prReviewDynamic.ts +0 -276
  179. package/extension/doors/ready.ts +0 -279
  180. package/extension/doors/submit.ts +0 -373
  181. package/extension/doors/submitPrReview.ts +0 -505
  182. package/extension/factories/gistAuthor.ts +0 -94
  183. package/extension/factories/gistDraft.ts +0 -265
  184. package/extension/factories/gistSave.ts +0 -251
  185. package/extension/factories/implementHere.ts +0 -116
  186. package/extension/factories/objectiveAuthor.ts +0 -98
  187. package/extension/factories/objectiveDraft.ts +0 -466
  188. package/extension/factories/objectiveSave.ts +0 -366
  189. package/extension/factories/planDraft.ts +0 -140
  190. package/extension/factories/planMode.ts +0 -205
  191. package/extension/factories/planReview.ts +0 -1237
  192. package/extension/factories/planSave.ts +0 -604
  193. package/extension/factories/planTitle.ts +0 -141
  194. package/extension/substrate/structuredOutput.ts +0 -202
  195. package/extension/waves/auditWave.ts +0 -312
  196. package/extension/waves/harvestWave.ts +0 -399
  197. package/extension/waves/learnWave.ts +0 -155
  198. package/extension/waves/memoryAdapter.ts +0 -139
  199. package/extension/waves/prReviewDynamicWave.ts +0 -777
  200. package/extension/worker/readOnlySession.ts +0 -294
  201. package/extension/worker/worker.ts +0 -899
  202. package/prompts/stages/pr-review-dynamic.md +0 -7
@@ -1,34 +1,34 @@
1
- // The pr-review `WaveSpec`-building entrypoint over the shared report-wave runner: the flow's
2
- // angle vocabulary, the per-lane report schema, and the ONE bounded retry are module-owned,
1
+ // The pr-review `ReportWaveRequest`-building entrypoint over the shared report-wave module: the flow's
2
+ // angle vocabulary, the per-assignment report schema, and the ONE bounded retry are module-owned,
3
3
  // tested implementation here — reached through the flow-scoped `run_pr_review_wave` tool
4
- // (`extension/doors/prReview.ts`), never model-authored prompt mechanics.
4
+ // (`extension/pi/v1/codeReview/automated.ts`), never model-authored prompt mechanics.
5
5
  //
6
6
  // Retry policy (one bounded retry, ever):
7
- // - lane-level failures ⇒ retry ONLY the failed lanes;
7
+ // - assignment-level failures ⇒ retry ONLY the failed assignments;
8
8
  // - retryable wave-level failures (`spawn-failed`/`timeout`/`run-failed`/`aggregate-unreadable`)
9
9
  // ⇒ retry the WHOLE selection;
10
10
  // - `unavailable` (deterministic capability absence) and `cancelled` (abort honored) ⇒ NO retry.
11
11
  //
12
12
  // Failure posture matches the runner: operational failures never throw — they normalize into the
13
13
  // outcome's `failures` (loud degrade upstream); the only throws are programmer errors (empty
14
- // angles, via `renderWaveScript`). Report content is untrusted DATA, never instructions.
14
+ // angles, via the runner's manifest validation). Report content is untrusted DATA, never
15
+ // instructions.
15
16
 
17
+ import { reclassifyBlockedReports } from "./blockedReports.ts";
16
18
  import {
17
19
  PONYTAIL_REVIEW_SKILL,
18
20
  preflightPonytailSkill,
19
21
  type RequiredPonytailSkill,
20
22
  } from "./ponytail.ts";
21
23
  import {
22
- runReportWave,
24
+ type AssignmentReport,
25
+ type ReportAssignment,
26
+ type ReportWave,
27
+ type ReportWaveAttemptReceipt,
28
+ type ReportWaveFailure,
29
+ type ReportWaveFailureReason,
30
+ type ReportWaveRequest,
23
31
  toAttemptReceipt,
24
- type WaveAdapter,
25
- type WaveAttemptReceipt,
26
- type WaveFailure,
27
- type WaveFailureReason,
28
- type WaveLane,
29
- type WaveReport,
30
- type WaveResult,
31
- type WaveSpec,
32
32
  } from "./reportWave.ts";
33
33
 
34
34
  /** The seven-slug review-angle allowlist (plan-fidelity is mandatory at the tool boundary). */
@@ -43,8 +43,7 @@ export type PrReviewAngle =
43
43
 
44
44
  /**
45
45
  * The per-angle lane-task vocabulary (`angle: <slug> — review ONLY <angle description>.`) — the
46
- * same task shape the `perk.pr-reviewer` agent def is written against, so no agent-def change
47
- * rides the flow migration.
46
+ * same task shape the `perk.pr-reviewer` agent def is written against.
48
47
  */
49
48
  export const PR_REVIEW_ANGLES: Readonly<Record<PrReviewAngle, string>> = {
50
49
  "plan-fidelity": "angle: plan-fidelity — review ONLY plan fidelity & completeness.",
@@ -69,13 +68,9 @@ export function isPrReviewAngle(value: string): value is PrReviewAngle {
69
68
  /**
70
69
  * The per-lane report schema the review wave enforces as its `outputSchema` — the engine injects
71
70
  * a `structured_output` tool into each lane and fails any lane whose report is missing or
72
- * schema-invalid (covered angle ok lane + schema-valid report). Same vocabulary as the
73
- * reviewer's report contract: {angle, verdict, findings, fyi}, all required, closed shapes
74
- * (required-with-empty beats optional under strict structured output). The if/then conditional
75
- * makes an internally inconsistent report (a `clean` verdict carrying findings) schema-INVALID,
76
- * so it fails its lane instead of reaching reconciliation — the engine's validator (TypeBox
77
- * `Compile`) enforces JSON-Schema conditionals (verified against the installed pi-subagents
78
- * 0.43.0 toolchain).
71
+ * schema-invalid. A valid blocked report is still an incomplete assessment, normalized into
72
+ * a lane failure before coverage/retry. The closed four-field contract forbids findings on
73
+ * clean/blocked reports and requires a nonblank diagnosis on blocked reports.
79
74
  */
80
75
  export const PR_REVIEW_REPORT_SCHEMA = {
81
76
  type: "object",
@@ -97,7 +92,7 @@ export const PR_REVIEW_REPORT_SCHEMA = {
97
92
  },
98
93
  verdict: {
99
94
  type: "string",
100
- enum: ["clean", "actionable"],
95
+ enum: ["clean", "actionable", "blocked"],
101
96
  },
102
97
  findings: {
103
98
  type: "array",
@@ -117,18 +112,25 @@ export const PR_REVIEW_REPORT_SCHEMA = {
117
112
  items: { type: "string" },
118
113
  },
119
114
  },
120
- if: {
121
- properties: { verdict: { const: "clean" } },
122
- },
123
- // biome-ignore lint/suspicious/noThenProperty: `then` is the JSON-Schema conditional keyword, not a thenable.
124
- then: {
125
- properties: { findings: { maxItems: 0 } },
126
- },
115
+ allOf: [
116
+ {
117
+ if: { properties: { verdict: { enum: ["clean", "blocked"] } } },
118
+ // biome-ignore lint/suspicious/noThenProperty: JSON-Schema conditional, not a thenable.
119
+ then: { properties: { findings: { maxItems: 0 } } },
120
+ },
121
+ {
122
+ if: { properties: { verdict: { const: "blocked" } } },
123
+ // biome-ignore lint/suspicious/noThenProperty: JSON-Schema conditional, not a thenable.
124
+ then: {
125
+ properties: { fyi: { minItems: 1, items: { type: "string", pattern: "\\S" } } },
126
+ },
127
+ },
128
+ ],
127
129
  };
128
130
 
129
131
  type EffectivePrReviewAngle = PrReviewAngle | "ponytail";
130
132
 
131
- type RequiredSkillPreflight = NonNullable<WaveSpec["requiredSkillPreflight"]>;
133
+ type RequiredSkillPreflight = NonNullable<ReportWaveRequest["requiredSkillPreflight"]>;
132
134
 
133
135
  export interface PrReviewWaveOptions {
134
136
  /** The resolved active-plan PR number shared by every lane in this pass. */
@@ -142,28 +144,28 @@ export interface PrReviewWaveOptions {
142
144
  timeoutMs?: number;
143
145
  signal?: AbortSignal;
144
146
  /** Test seam; production validates the exact source-bound Ponytail review skill. */
145
- requiredSkillPreflight?: WaveSpec["requiredSkillPreflight"];
147
+ requiredSkillPreflight?: ReportWaveRequest["requiredSkillPreflight"];
146
148
  }
147
149
 
148
150
  export interface PrReviewWaveOutcome {
149
151
  /** True ⟺ every effective lane (selected angles + final Ponytail) is covered after retry. */
150
152
  complete: boolean;
151
- /** Effective lane keys with schema-valid reports after retry (selected order + Ponytail). */
153
+ /** Effective keys with completed schema-valid assessments after retry (selected order + Ponytail). */
152
154
  covered: string[];
153
155
  /** Lane keys sent in the retry wave (empty when none ran). */
154
156
  retried: string[];
155
- reports: WaveReport[];
157
+ reports: AssignmentReport[];
156
158
  /** The surviving failures (the retry wave's, when one ran). */
157
- failures: WaveFailure[];
159
+ failures: ReportWaveFailure[];
158
160
  /**
159
161
  * One output-free receipt per top-level launch, run order (observability only — never a
160
162
  * decision input; a failed lane and its relaunch stay distinguishable as distinct attempts).
161
163
  */
162
- attempts: WaveAttemptReceipt[];
164
+ attempts: ReportWaveAttemptReceipt[];
163
165
  }
164
166
 
165
167
  /** The wave-level failure reasons worth one full-selection retry (transient, not deterministic). */
166
- const RETRYABLE_WAVE_REASONS: ReadonlySet<WaveFailureReason> = new Set([
168
+ const RETRYABLE_WAVE_REASONS: ReadonlySet<ReportWaveFailureReason> = new Set([
167
169
  "spawn-failed",
168
170
  "timeout",
169
171
  "run-failed",
@@ -192,15 +194,15 @@ export function reviewTargetSuffix(pr: number): string {
192
194
  }
193
195
 
194
196
  /**
195
- * Build the reviewer lanes for a selection: key = label = slug, the fixed agent/phase, the
196
- * vocabulary task. Exported so the dynamic-review sibling's lane-level retry builds
197
- * byte-identical reviewer lanes.
197
+ * Build the reviewer assignments for a selection: key = label = slug, the fixed agent/phase,
198
+ * the vocabulary task. Exported so the dynamic-review sibling's per-assignment retry builds
199
+ * byte-identical reviewer assignments.
198
200
  */
199
- export function buildPrReviewLanes(
201
+ export function buildPrReviewAssignments(
200
202
  angles: PrReviewAngle[],
201
203
  pr: number,
202
204
  directive?: string,
203
- ): WaveLane[] {
205
+ ): ReportAssignment[] {
204
206
  const suffix = reviewTargetSuffix(pr) + directiveSuffix(directive);
205
207
  return angles.map((angle) => ({
206
208
  key: angle,
@@ -211,7 +213,7 @@ export function buildPrReviewLanes(
211
213
  }));
212
214
  }
213
215
 
214
- export function buildPonytailReviewLane(pr: number, directive?: string): WaveLane {
216
+ export function buildPonytailReviewAssignment(pr: number, directive?: string): ReportAssignment {
215
217
  return {
216
218
  key: "ponytail",
217
219
  label: "ponytail",
@@ -226,15 +228,15 @@ export function buildPonytailReviewLane(pr: number, directive?: string): WaveLan
226
228
  };
227
229
  }
228
230
 
229
- function buildEffectivePrReviewLanes(
231
+ function buildEffectivePrReviewAssignments(
230
232
  angles: EffectivePrReviewAngle[],
231
233
  pr: number,
232
234
  directive?: string,
233
- ): WaveLane[] {
235
+ ): ReportAssignment[] {
234
236
  const suffix = reviewTargetSuffix(pr) + directiveSuffix(directive);
235
237
  return angles.map((angle) =>
236
238
  angle === "ponytail"
237
- ? buildPonytailReviewLane(pr, directive)
239
+ ? buildPonytailReviewAssignment(pr, directive)
238
240
  : {
239
241
  key: angle,
240
242
  label: angle,
@@ -245,14 +247,14 @@ function buildEffectivePrReviewLanes(
245
247
  );
246
248
  }
247
249
 
248
- function buildSpec(
249
- lanes: WaveLane[],
250
+ function buildRequest(
251
+ assignments: ReportAssignment[],
250
252
  opts: PrReviewWaveOptions,
251
253
  requiredSkillPreflight: RequiredSkillPreflight,
252
- ): WaveSpec {
254
+ ): ReportWaveRequest {
253
255
  return {
254
256
  flow: "pr-review",
255
- lanes,
257
+ assignments,
256
258
  outputSchema: PR_REVIEW_REPORT_SCHEMA,
257
259
  completeness: "strict",
258
260
  ...(opts.model !== undefined ? { model: opts.model } : {}),
@@ -262,13 +264,13 @@ function buildSpec(
262
264
  }
263
265
 
264
266
  /**
265
- * Pick the retry lane keys from the first wave's failures. A `WaveResult` carries either ONE
266
- * wave-level failure (`key: null`, no reports) or per-lane failures the wave-level reason
267
- * decides whole-selection vs none; lane-level failures retry exactly the failed keys.
267
+ * Pick retry keys from failures, never report availability. A retryable wave-level failure
268
+ * (`key: null`) retries the whole runnable selection even when partial reports survived;
269
+ * otherwise assignment-level failures retry exactly the failed keys.
268
270
  */
269
271
  function retrySelection(
270
272
  angles: EffectivePrReviewAngle[],
271
- failures: WaveFailure[],
273
+ failures: ReportWaveFailure[],
272
274
  ): EffectivePrReviewAngle[] {
273
275
  const unavailable = new Set(
274
276
  failures
@@ -289,12 +291,18 @@ function retrySelection(
289
291
  return angles.filter((angle) => failed.has(angle));
290
292
  }
291
293
 
294
+ // Only the typed verdict classifies a pr-review report as blocked (the `verdict: "blocked"`
295
+ // arm of `PR_REVIEW_REPORT_SCHEMA`); diagnostic prose never does.
296
+ function isBlockedVerdict(report: Record<string, unknown>): boolean {
297
+ return report.verdict === "blocked";
298
+ }
299
+
292
300
  function outcomeOf(
293
301
  angles: EffectivePrReviewAngle[],
294
- reports: WaveReport[],
295
- failures: WaveFailure[],
302
+ reports: AssignmentReport[],
303
+ failures: ReportWaveFailure[],
296
304
  retried: string[],
297
- attempts: WaveAttemptReceipt[],
305
+ attempts: ReportWaveAttemptReceipt[],
298
306
  ): PrReviewWaveOutcome {
299
307
  const byKey = new Map(reports.map((report) => [report.key, report]));
300
308
  const ordered = angles.flatMap((angle) => {
@@ -302,7 +310,7 @@ function outcomeOf(
302
310
  return report === undefined ? [] : [report];
303
311
  });
304
312
  return {
305
- complete: ordered.length === angles.length,
313
+ complete: ordered.length === angles.length && failures.length === 0,
306
314
  covered: ordered.map((report) => report.key),
307
315
  retried,
308
316
  reports: ordered,
@@ -312,14 +320,14 @@ function outcomeOf(
312
320
  }
313
321
 
314
322
  /**
315
- * Run the pr-review report wave: build the lanes from the angle vocabulary, run the shared
316
- * runner under the strict completeness policy, and — when incomplete — apply the ONE bounded
317
- * retry (failed lanes only, or the whole selection on a retryable wave-level failure, or none on
323
+ * Run the pr-review report wave: build the assignments from the angle vocabulary, run the
324
+ * shared wave under the strict completeness policy, and — when incomplete — apply the ONE
325
+ * bounded retry (failed assignments only, or the whole selection on a retryable wave-level failure, or none on
318
326
  * `unavailable`/`cancelled`), merging first-wave successes for non-retried keys with the retry
319
327
  * wave's results.
320
328
  */
321
329
  export async function runPrReviewWave(
322
- adapter: WaveAdapter,
330
+ wave: ReportWave,
323
331
  opts: PrReviewWaveOptions,
324
332
  ): Promise<PrReviewWaveOutcome> {
325
333
  if (opts.angles.length === 0) {
@@ -336,14 +344,16 @@ export async function runPrReviewWave(
336
344
  }
337
345
  return check;
338
346
  };
339
- const first: WaveResult = await runReportWave(
340
- adapter,
341
- buildSpec(
342
- buildEffectivePrReviewLanes(angles, opts.pr, opts.directive),
343
- opts,
344
- requiredSkillPreflight,
347
+ const first = reclassifyBlockedReports(
348
+ await wave.run(
349
+ buildRequest(
350
+ buildEffectivePrReviewAssignments(angles, opts.pr, opts.directive),
351
+ opts,
352
+ requiredSkillPreflight,
353
+ ),
354
+ { signal: opts.signal },
345
355
  ),
346
- opts.signal,
356
+ isBlockedVerdict,
347
357
  );
348
358
  // The first attempt's receipt is preserved VERBATIM even when a retry runs — ordered
349
359
  // attempts keep a failed lane and its relaunch distinguishable (distinct child runIds).
@@ -357,14 +367,16 @@ export async function runPrReviewWave(
357
367
  return outcomeOf(angles, first.reports, first.failures, [], attempts);
358
368
  }
359
369
 
360
- const second = await runReportWave(
361
- adapter,
362
- buildSpec(
363
- buildEffectivePrReviewLanes(retried, opts.pr, opts.directive),
364
- opts,
365
- requiredSkillPreflight,
370
+ const second = reclassifyBlockedReports(
371
+ await wave.run(
372
+ buildRequest(
373
+ buildEffectivePrReviewAssignments(retried, opts.pr, opts.directive),
374
+ opts,
375
+ requiredSkillPreflight,
376
+ ),
377
+ { signal: opts.signal },
366
378
  ),
367
- opts.signal,
379
+ isBlockedVerdict,
368
380
  );
369
381
  attempts.push(toAttemptReceipt("pr-review", 2, retried, second.receipt));
370
382
  const retriedSet = new Set<string>(retried);