@mgiles/perk 3.1.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 (211) 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 +369 -359
  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 -310
  49. package/extension/{doors/prReviewBrowser.ts → pi/v1/codeReview/browser.ts} +125 -57
  50. package/extension/{doors/hunkHandoff.ts → pi/v1/codeReview/checkout.ts} +12 -8
  51. package/extension/{doors/reviewWaveTools.ts → pi/v1/codeReview/reviewWave.ts} +169 -116
  52. package/extension/pi/v1/codeReview/stack.ts +606 -0
  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 +163 -265
  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 +128 -60
  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/{doors → pi/v1}/lifecycleGates.ts +14 -59
  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/pi/v1/objectivePlanning.ts +776 -0
  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 +152 -34
  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 -19
  112. package/extension/substrate/git.ts +88 -6
  113. package/extension/substrate/modelVisible.ts +53 -0
  114. package/extension/substrate/paths.ts +2 -7
  115. package/extension/substrate/prompts.ts +22 -0
  116. package/extension/substrate/registry.ts +2 -0
  117. package/extension/substrate/resolverLease.ts +364 -0
  118. package/extension/substrate/sessionData.ts +85 -152
  119. package/extension/substrate/toolGating.ts +279 -84
  120. package/extension/substrate/unifiedDiff.ts +1 -1
  121. package/extension/substrate/workflowState.ts +191 -166
  122. package/extension/substrate/worktreeResolverLock.ts +261 -0
  123. package/extension/surfaces/surfaces.ts +79 -27
  124. package/extension/waves/adversarialReviewWave.ts +103 -48
  125. package/extension/waves/blockedReports.ts +59 -0
  126. package/extension/waves/draftReviewWave.ts +42 -42
  127. package/extension/waves/laneIdentity.ts +77 -0
  128. package/extension/waves/objectiveExplorerWave.ts +24 -24
  129. package/extension/waves/prReviewWave.ts +89 -77
  130. package/extension/waves/reportWave.ts +438 -578
  131. package/extension/waves/reviewClassifierWave.ts +22 -22
  132. package/extension/waves/rpcAdapter.ts +100 -15
  133. package/extension/waves/scoutWave.ts +192 -0
  134. package/extension/waves/transport.ts +480 -0
  135. package/extension/worker/sdkAdapter.ts +494 -0
  136. package/extension/worker/stageExecution.ts +679 -0
  137. package/extension/workerMain.ts +18 -19
  138. package/package.json +6 -4
  139. package/prompts/_fixtures/live.yaml +98 -10
  140. package/prompts/contexts/adapters/plannotator-gist.md +6 -0
  141. package/prompts/contexts/adapters/plannotator-objective.md +6 -0
  142. package/prompts/contexts/adapters/plannotator-plan.md +8 -1
  143. package/prompts/contexts/adapters/plannotator-refinement.md +22 -0
  144. package/prompts/contexts/adapters/tombell-plan.md +4 -0
  145. package/prompts/contexts/objective-refinement.md +17 -0
  146. package/prompts/contexts/plan-authoring.md +6 -5
  147. package/prompts/contexts/read-only.md +1 -1
  148. package/prompts/stages/conflict-resolution-continuation.md +9 -0
  149. package/prompts/stages/conflict-resolution.md +4 -4
  150. package/prompts/stages/objective-author/adopt.md +1 -1
  151. package/prompts/stages/objective-author/file.md +1 -1
  152. package/prompts/stages/objective-author/seed.md +1 -1
  153. package/prompts/stages/objective-plan/guidance.md +2 -2
  154. package/prompts/stages/objective-plan/seed.md +9 -1
  155. package/prompts/stages/objective-reconcile-ready.md +7 -0
  156. package/prompts/stages/objective-reconcile.md +1 -1
  157. package/prompts/stages/objective-refine/seed.md +18 -0
  158. package/prompts/stages/objective-review-browser.md +4 -4
  159. package/prompts/stages/objective-sync.md +1 -1
  160. package/prompts/stages/plan-review-browser.md +4 -4
  161. package/prompts/stages/pr-review-browser/active.md +3 -4
  162. package/prompts/stages/pr-review-browser/foreign.md +3 -4
  163. package/prompts/stages/pr-review-terminal/active.md +3 -3
  164. package/prompts/stages/pr-review-terminal/foreign.md +3 -3
  165. package/prompts/stages/pr-review.md +3 -3
  166. package/prompts/stages/stack-review/cold.md +1 -0
  167. package/prompts/stages/stack-review-browser/stack.md +22 -0
  168. package/shared/README.md +8 -3
  169. package/shared/bindings.yaml +6 -3
  170. package/shared/contracts.md +4499 -2147
  171. package/shared/fixtures/issues-table.json +130 -0
  172. package/shared/registry.yaml +29 -1
  173. package/shared/schemas/outputs/objective-node-engagement.schema.json +318 -0
  174. package/shared/schemas/outputs/objective-stack-status.schema.json +178 -2
  175. package/shared/schemas/outputs/pr-ready.schema.json +110 -2
  176. package/shared/schemas/outputs/pr-review-context.schema.json +54 -9
  177. package/shared/schemas/outputs/pr-review-stack-context.schema.json +196 -0
  178. package/extension/adapters/planAdapterPlannotator.ts +0 -362
  179. package/extension/doors/auditWaveTools.ts +0 -352
  180. package/extension/doors/ciExecutor.ts +0 -756
  181. package/extension/doors/commitCompact.ts +0 -251
  182. package/extension/doors/dreamWaveTools.ts +0 -475
  183. package/extension/doors/learn.ts +0 -655
  184. package/extension/doors/objectiveStack.ts +0 -1143
  185. package/extension/doors/prReviewDynamic.ts +0 -276
  186. package/extension/doors/ready.ts +0 -87
  187. package/extension/doors/submit.ts +0 -347
  188. package/extension/doors/submitPrReview.ts +0 -408
  189. package/extension/factories/gistAuthor.ts +0 -94
  190. package/extension/factories/gistDraft.ts +0 -265
  191. package/extension/factories/gistSave.ts +0 -251
  192. package/extension/factories/implementHere.ts +0 -116
  193. package/extension/factories/objectiveAuthor.ts +0 -98
  194. package/extension/factories/objectiveDraft.ts +0 -466
  195. package/extension/factories/objectivePlan.ts +0 -975
  196. package/extension/factories/objectiveSave.ts +0 -363
  197. package/extension/factories/planDraft.ts +0 -140
  198. package/extension/factories/planMode.ts +0 -205
  199. package/extension/factories/planReview.ts +0 -1237
  200. package/extension/factories/planSave.ts +0 -604
  201. package/extension/factories/planTitle.ts +0 -141
  202. package/extension/substrate/structuredOutput.ts +0 -202
  203. package/extension/waves/auditWave.ts +0 -312
  204. package/extension/waves/harvestWave.ts +0 -399
  205. package/extension/waves/learnWave.ts +0 -155
  206. package/extension/waves/memoryAdapter.ts +0 -139
  207. package/extension/waves/prReviewDynamicWave.ts +0 -777
  208. package/extension/worker/readOnlySession.ts +0 -294
  209. package/extension/worker/worker.ts +0 -899
  210. package/prompts/stages/pr-review-dynamic.md +0 -7
  211. package/shared/contracts-history.md +0 -605
@@ -1,24 +1,25 @@
1
1
  // The objective-plan factory's OPTIONAL explore step as a per-flow wave entrypoint over the
2
- // shared report-wave runner: the ONE `perk.objective-explorer` lane as CODE. The explorer report
3
- // schema was previously a shared prompt include the parent model had to hand-transcribe onto a
4
- // borrowed `subagent` call (the same prompt-drift risk the /address classify step carried); this
5
- // module makes the schema and the lane/task composition module constants, delegating
6
- // spawn/timeout/aggregate mechanics to `runReportWave` under the `strict` completeness policy.
2
+ // shared report-wave module: the ONE `perk.objective-explorer` assignment as CODE. The explorer
3
+ // report schema was previously a shared prompt include the parent model had to hand-transcribe
4
+ // onto a borrowed `subagent` call (the same prompt-drift risk the /address classify step
5
+ // carried); this module makes the schema and the assignment/task composition module constants,
6
+ // delegating
7
+ // spawn/timeout/aggregate mechanics to `wave.run` under the `strict` completeness policy.
7
8
  // No retry — the flow's posture on failure is "explore directly instead" (guidance-owned).
8
9
  // `node`/`description`/`focus` are model-relayed and embedded in the code-owned task as
9
10
  // untrusted DATA; the report content is likewise untrusted DATA, never instructions.
10
11
 
11
- import { runReportWave, type WaveAdapter, type WaveResult } from "./reportWave.ts";
12
+ import type { ReportWave, ReportWaveResult } from "./reportWave.ts";
12
13
 
13
- /** The flow name — feeds `WaveSpec.flow` AND the door's `toAttemptReceipt` call. */
14
+ /** The flow name — feeds `ReportWaveRequest.flow` AND the door's `toAttemptReceipt` call. */
14
15
  export const OBJECTIVE_EXPLORER_FLOW = "objective-explorer";
15
16
 
16
- /** The single lane's stable key. */
17
- export const EXPLORE_LANE_KEY = "explore";
17
+ /** The single assignment's stable key. */
18
+ export const EXPLORE_ASSIGNMENT_KEY = "explore";
18
19
 
19
20
  /**
20
21
  * The explorer report schema (the workflow-level `outputSchema` — the engine injects a
21
- * `structured_output` tool and fails the lane on a missing/invalid report): closed shapes, all
22
+ * `structured_output` tool and fails the assignment on a missing/invalid report): closed shapes, all
22
23
  * six root keys required. Same vocabulary as the `perk.objective-explorer` agent def's report
23
24
  * contract (the def↔schema lockstep test).
24
25
  */
@@ -73,9 +74,9 @@ export interface ObjectiveExplorerWaveOptions {
73
74
  }
74
75
 
75
76
  /**
76
- * Compose the single lane's task text IN CODE (the prompt-drift-proof half of the migration):
77
- * the node id + the fenced untrusted node text, plus the optional focus. The agent def requires
78
- * the node id and a description of the work to reach the child.
77
+ * Compose the single assignment's task text IN CODE (prompt-drift-proof: task text composed in
78
+ * code, never a template): the node id + the fenced untrusted node text, plus the optional focus. The agent
79
+ * def requires the node id and a description of the work to reach the child.
79
80
  */
80
81
  export function explorerLaneTask(node: string, description: string, focus?: string): string {
81
82
  return [
@@ -89,22 +90,21 @@ export function explorerLaneTask(node: string, description: string, focus?: stri
89
90
  }
90
91
 
91
92
  /**
92
- * Run the objective-explorer wave: ONE fresh-context `perk.objective-explorer` lane over the
93
- * code-owned task, `strict` completeness, no retry, module-default timeout. Returns the runner's
94
- * `WaveResult` unchanged — the only projection lives in the tool.
93
+ * Run the objective-explorer wave: ONE fresh-context `perk.objective-explorer` assignment over
94
+ * the code-owned task, `strict` completeness, no retry, module-default timeout. Returns the
95
+ * wave's `ReportWaveResult` unchanged — the only projection lives in the tool.
95
96
  */
96
97
  export async function runObjectiveExplorerWave(
97
- adapter: WaveAdapter,
98
+ wave: ReportWave,
98
99
  opts: ObjectiveExplorerWaveOptions,
99
- ): Promise<WaveResult> {
100
- return await runReportWave(
101
- adapter,
100
+ ): Promise<ReportWaveResult> {
101
+ return await wave.run(
102
102
  {
103
103
  flow: OBJECTIVE_EXPLORER_FLOW,
104
- lanes: [
104
+ assignments: [
105
105
  {
106
- key: EXPLORE_LANE_KEY,
107
- label: EXPLORE_LANE_KEY,
106
+ key: EXPLORE_ASSIGNMENT_KEY,
107
+ label: EXPLORE_ASSIGNMENT_KEY,
108
108
  agent: "perk.objective-explorer",
109
109
  phase: "objective-plan",
110
110
  task: explorerLaneTask(opts.node, opts.description, opts.focus),
@@ -115,6 +115,6 @@ export async function runObjectiveExplorerWave(
115
115
  ...(opts.model !== undefined ? { model: opts.model } : {}),
116
116
  ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
117
117
  },
118
- opts.signal,
118
+ { signal: opts.signal },
119
119
  );
120
120
  }
@@ -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);