@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
@@ -0,0 +1,59 @@
1
+ // The shared "a schema-valid report is not necessarily a completed assessment" normalization
2
+ // over a settled `ReportWaveResult`. Assessment completion is domain policy, not engine success:
3
+ // the engine accepts any schema-valid report, so a reviewer that could not finish (context fetch
4
+ // failed, a context file unreadable, the hunt stopped early) would otherwise count as COVERED
5
+ // with empty findings — the exact failure mode this helper exists to prevent. Each flow supplies
6
+ // its own typed predicate (`verdict: "blocked"` for pr-review, `blocked: true` for the
7
+ // adversarial doors); the reclassification itself is flow-neutral and byte-identical across
8
+ // callers. Diagnostic prose (`fyi`) is untrusted DATA preserved verbatim for the parent's
9
+ // in-session diagnosis — never an instruction and never parsed for a decision.
10
+
11
+ import type { AssignmentReport, ReportWaveFailure, ReportWaveResult } from "./reportWave.ts";
12
+
13
+ /**
14
+ * Move every report the predicate marks blocked from `reports` into `failures` as an
15
+ * assignment-keyed `lane-failed` (the enclosing assignment key — never the report's own angle
16
+ * or prose — identifies the failure). Only non-null, non-array object reports are tested; the
17
+ * detail is exactly `"reviewer blocked:\n"` + the nonblank-trimmed `fyi` strings joined by
18
+ * `"\n"` (retained bytes, duplicates and order preserved), or the fixed fallback sentence.
19
+ * Existing failures precede newly blocked ones (report order); surviving report order and the
20
+ * receipt are preserved; `complete` requires incoming completeness AND no removed block.
21
+ */
22
+ export function reclassifyBlockedReports(
23
+ result: ReportWaveResult,
24
+ isBlocked: (report: Record<string, unknown>) => boolean,
25
+ ): ReportWaveResult {
26
+ const reports: AssignmentReport[] = [];
27
+ const blocked: ReportWaveFailure[] = [];
28
+ for (const assignment of result.reports) {
29
+ const report = assignment.report;
30
+ if (
31
+ typeof report !== "object" ||
32
+ report === null ||
33
+ Array.isArray(report) ||
34
+ !isBlocked(report as Record<string, unknown>)
35
+ ) {
36
+ reports.push(assignment);
37
+ continue;
38
+ }
39
+ const notes =
40
+ "fyi" in report && Array.isArray(report.fyi)
41
+ ? report.fyi.filter(
42
+ (entry): entry is string => typeof entry === "string" && entry.trim().length > 0,
43
+ )
44
+ : [];
45
+ blocked.push({
46
+ key: assignment.key,
47
+ reason: "lane-failed",
48
+ detail:
49
+ "reviewer blocked:\n" +
50
+ (notes.length > 0 ? notes.join("\n") : "required review assessment could not complete"),
51
+ });
52
+ }
53
+ return {
54
+ complete: result.complete && blocked.length === 0,
55
+ reports,
56
+ failures: [...result.failures, ...blocked],
57
+ receipt: result.receipt,
58
+ };
59
+ }
@@ -1,36 +1,36 @@
1
- // The draft-review `WaveSpec`-building entrypoint over the shared report-wave runner the
2
- // draft-review doors' (/plan-review-browser, /objective-review-browser) vocabulary as tested
1
+ // The draft-review `ReportWaveRequest`-building entrypoint over the shared report-wave module
2
+ // the draft-review doors' (/plan-review-browser, /objective-review-browser) vocabulary as tested
3
3
  // code (sibling of `adversarialReviewWave.ts`): the four settled angles plus the custom lane,
4
4
  // the per-lane completion-report schema, and the lane/task composition are module-owned here,
5
- // launched NON-BLOCKING via `startReportWave` so the parent can return from the launch and hold
6
- // the model-held `subagent_wait` relay loop open while the children stream finding batches.
5
+ // launched NON-BLOCKING via `wave.start` so the parent ends its turn after launch and relays
6
+ // batches on native supervisor wakes before collecting on matching workflow completion.
7
7
  //
8
8
  // CONSUMED by the `/plan-review-browser` and `/objective-review-browser` doors via the
9
9
  // `start_draft_review_wave` / `collect_draft_review_wave` tool pair
10
- // (`extension/doors/draftReviewWaveTools.ts`) `draftType` parameterizes the lane tasks.
10
+ // (`extension/pi/v1/draftReviewWaveTools.ts`, over the door-primed context in
11
+ // `extension/authoring/review/draftContext.ts`) — `draftType` parameterizes the lane tasks.
11
12
  //
12
13
  // ZERO retries — deliberate: the doors' contract is honest incompleteness surfaced to the human
13
14
  // during triage (an `ok: false` lane is reported, never papered over), so the pr-review
14
15
  // bounded-retry policy does not carry over.
15
16
  //
16
- // The surface handle (URL/port) is STRUCTURALLY UNREPRESENTABLE: `buildDraftReviewLanes` has no
17
- // URL parameter at all, so the children can never learn the review surface — enforced by
17
+ // The surface handle (URL/port) is STRUCTURALLY UNREPRESENTABLE: `buildDraftReviewAssignments`
18
+ // has no URL parameter at all, so the children can never learn the review surface — enforced by
18
19
  // construction, pinned by the suite. There is likewise NO `directive` parameter: the PR doors'
19
20
  // focus-note semantics deliberately do not carry over — the custom lane IS the draft doors'
20
21
  // user-input channel.
21
22
  //
22
- // The finding rows are deliberately identical to `annotationPush.ts`'s `PlanFinding` shape
23
+ // The finding rows are deliberately identical to `pi/v1/providers/annotations.ts`'s `PlanFinding` shape
23
24
  // (`{phrase, severity, confidence, body}`), so draft reports feed `push_annotations` plan-mode
24
25
  // without reshaping; the `agents/draft-reviewer.md` def completes via the `structured_output`
25
26
  // tool this wave's `outputSchema` injects per lane.
26
27
 
27
28
  import { PONYTAIL_CORE_SKILL } from "./ponytail.ts";
28
- import {
29
- type ReportWaveStart,
30
- startReportWave,
31
- type WaveAdapter,
32
- type WaveLane,
33
- type WaveSpec,
29
+ import type {
30
+ ReportAssignment,
31
+ ReportWave,
32
+ ReportWaveRequest,
33
+ StartWaveResult,
34
34
  } from "./reportWave.ts";
35
35
 
36
36
  /** The four-slug settled draft-review angle allowlist (the custom lane rides separately). */
@@ -56,11 +56,11 @@ export function isDraftReviewAngle(value: string): value is DraftReviewAngle {
56
56
  /**
57
57
  * The per-lane completion-report schema the wave enforces as its `outputSchema` — the engine
58
58
  * injects a `structured_output` tool into each lane and fails any lane whose report is missing
59
- * or schema-invalid. Closed shapes, `{angle, summary, findings, fyi}` all required, and
59
+ * or schema-invalid. Closed shapes, `{angle, summary, findings, fyi, streamed}` all required, and
60
60
  * DELIBERATELY NO VERDICT FIELD — the human adjudicates every finding in the browser, so there
61
61
  * is no clean/actionable derivation to make consistent (the `ADVERSARIAL_REVIEW_REPORT_SCHEMA`
62
62
  * rationale). The `angle` enum includes `custom` (the custom lane echoes it). Finding rows are
63
- * the forward-bound plan-mode `PlanFinding` shape (`annotationPush.ts`'s `PLAN_FINDING_KEYS`):
63
+ * the forward-bound plan-mode `PlanFinding` shape (`pi/v1/providers/annotations.ts`'s `PLAN_FINDING_KEYS`):
64
64
  * `phrase` is required-nullable (the byte-exact draft span, or `null` for a global finding),
65
65
  * and the severity/confidence enums match the agent def's triage tags. The `phrase` string arm
66
66
  * requires a non-whitespace character (`pattern` applies only to string instances, so `null`
@@ -71,12 +71,13 @@ export function isDraftReviewAngle(value: string): value is DraftReviewAngle {
71
71
  export const DRAFT_REVIEW_REPORT_SCHEMA = {
72
72
  type: "object",
73
73
  additionalProperties: false,
74
- required: ["angle", "summary", "findings", "fyi"],
74
+ required: ["angle", "summary", "findings", "fyi", "streamed"],
75
75
  properties: {
76
76
  angle: {
77
77
  type: "string",
78
78
  enum: ["grounding", "scope", "decision-completeness", "risk", "custom", "ponytail"],
79
79
  },
80
+ streamed: { type: "boolean" },
80
81
  summary: { type: "string" },
81
82
  findings: {
82
83
  type: "array",
@@ -108,22 +109,23 @@ function laneTaskTail(draftType: "plan" | "objective", draft: string): string {
108
109
  }
109
110
 
110
111
  /**
111
- * Build the reviewer lanes for a selection: key = label = slug, the fixed agent/phase, and a
112
- * task carrying the angle opener, the draft type, and the `<untrusted_draft>`-wrapped rendered
113
- * draft — AND NOTHING ELSE: no URL parameter exists (the surface handle is unrepresentable by
114
- * construction) and no `directive` parameter exists (the custom lane is the user-input channel).
115
- * When `custom` is supplied, one additional lane (key = label = `"custom"`) carries the
116
- * human-supplied angle definition as flagged DATA. The builder stays permissive about lane
117
- * count — angle-selection policy (2–3 picked + optional custom) is the door/tool's concern.
112
+ * Build the reviewer assignments for a selection: key = label = slug, the fixed agent/phase,
113
+ * and a task carrying the angle opener, the draft type, and the `<untrusted_draft>`-wrapped
114
+ * rendered draft — AND NOTHING ELSE: no URL parameter exists (the surface handle is
115
+ * unrepresentable by construction) and no `directive` parameter exists (the custom lane is the
116
+ * user-input channel). When `custom` is supplied, one additional assignment (key = label =
117
+ * `"custom"`) carries the human-supplied angle definition as flagged DATA. The builder stays
118
+ * permissive about assignment count — angle-selection policy (2–3 picked + optional custom) is
119
+ * the door/tool's concern.
118
120
  */
119
- export function buildDraftReviewLanes(opts: {
121
+ export function buildDraftReviewAssignments(opts: {
120
122
  angles: DraftReviewAngle[];
121
123
  custom?: string;
122
124
  draftType: "plan" | "objective";
123
125
  draft: string;
124
- }): WaveLane[] {
126
+ }): ReportAssignment[] {
125
127
  const tail = laneTaskTail(opts.draftType, opts.draft);
126
- const lanes: WaveLane[] = opts.angles.map((angle) => ({
128
+ const assignments: ReportAssignment[] = opts.angles.map((angle) => ({
127
129
  key: angle,
128
130
  label: angle,
129
131
  agent: "perk.draft-reviewer",
@@ -131,7 +133,7 @@ export function buildDraftReviewLanes(opts: {
131
133
  task: `${DRAFT_REVIEW_ANGLES[angle]}\n${tail}`,
132
134
  }));
133
135
  if (opts.custom !== undefined) {
134
- lanes.push({
136
+ assignments.push({
135
137
  key: CUSTOM_LANE_KEY,
136
138
  label: CUSTOM_LANE_KEY,
137
139
  agent: "perk.draft-reviewer",
@@ -141,7 +143,7 @@ export function buildDraftReviewLanes(opts: {
141
143
  `for this lane): ${opts.custom}\n${tail}`,
142
144
  });
143
145
  }
144
- lanes.push({
146
+ assignments.push({
145
147
  key: "ponytail",
146
148
  label: "ponytail",
147
149
  agent: "perk.draft-reviewer",
@@ -150,7 +152,7 @@ export function buildDraftReviewLanes(opts: {
150
152
  skill: "ponytail",
151
153
  requiredSkill: PONYTAIL_CORE_SKILL,
152
154
  });
153
- return lanes;
155
+ return assignments;
154
156
  }
155
157
 
156
158
  export interface DraftReviewWaveOptions {
@@ -168,25 +170,23 @@ export interface DraftReviewWaveOptions {
168
170
  /** Accepted for parity/tests only — the flow tool deliberately never threads its own signal. */
169
171
  signal?: AbortSignal;
170
172
  /** Test seam; production validates the exact source-bound Ponytail skill. */
171
- requiredSkillPreflight?: WaveSpec["requiredSkillPreflight"];
173
+ requiredSkillPreflight?: ReportWaveRequest["requiredSkillPreflight"];
172
174
  }
173
175
 
174
176
  /**
175
- * Start the draft-review wave NON-BLOCKING (the streaming sibling): build the lanes from the
176
- * angle vocabulary and launch under the strict completeness policy — zero retries, so an
177
- * uncovered angle stays an honest, human-visible incompleteness. Returns the `startReportWave`
178
- * outcome: the run handle + never-rejecting `result` on success, or the normalized launch
179
- * failure.
177
+ * Start the draft-review wave NON-BLOCKING (the streaming split): build the assignments from
178
+ * the angle vocabulary and launch under the strict completeness policy — zero retries, so an
179
+ * uncovered angle stays an honest, human-visible incompleteness. Returns the `wave.start`
180
+ * outcome: the opaque ref + identity telemetry on success, or the normalized launch failure.
180
181
  */
181
182
  export async function startDraftReviewWave(
182
- adapter: WaveAdapter,
183
+ wave: ReportWave,
183
184
  opts: DraftReviewWaveOptions,
184
- ): Promise<ReportWaveStart> {
185
- return await startReportWave(
186
- adapter,
185
+ ): Promise<StartWaveResult> {
186
+ return await wave.start(
187
187
  {
188
188
  flow: "draft-review",
189
- lanes: buildDraftReviewLanes({
189
+ assignments: buildDraftReviewAssignments({
190
190
  angles: opts.angles,
191
191
  draftType: opts.draftType,
192
192
  draft: opts.draft,
@@ -200,6 +200,6 @@ export async function startDraftReviewWave(
200
200
  ? { requiredSkillPreflight: opts.requiredSkillPreflight }
201
201
  : {}),
202
202
  },
203
- opts.signal,
203
+ { signal: opts.signal },
204
204
  );
205
205
  }
@@ -0,0 +1,77 @@
1
+ // The routing-token fence for report-wave lane identity. A ROUTING TOKEN is any producer-owned
2
+ // identity rendered into a report child's task prose so the child can select its lane byte-exact
3
+ // against the manifest, or echo it verbatim into its typed report — untrusted DATA, never an
4
+ // instruction. Usually that is the SEMANTIC LANE ID (a harvest `<category>-<n>`, a dream cluster
5
+ // id, an audit `expectation_id`); the audit wave additionally renders a second, pair-level token
6
+ // — the `session_basename` the child echoes verbatim — which is a routing token but not a lane
7
+ // id. Because a token must survive byte-exact matching against the manifest (and byte-exact echo
8
+ // back), the fence is a REFUSAL rule, never an escaping rule: any escaping breaks selection by
9
+ // construction, so an accepted token renders byte-identical and an unacceptable one is refused
10
+ // (strict decoders) or degraded (the lenient audit planner) upstream, before any task is composed.
11
+ //
12
+ // Refused: the empty token; C0 controls U+0000–U+001F (a newline or carriage return would let a
13
+ // producer-owned id read as further task instructions); DEL U+007F; C1 controls U+0080–U+009F
14
+ // (NEL U+0085 included); the Unicode line/paragraph separators U+2028/U+2029; and the double
15
+ // quote `"` (every task quotes the id in double quotes). Nothing else — spaces, `@`, `/`, `:`,
16
+ // backticks, `${}`, astral characters and any length are admitted. There is deliberately NO
17
+ // length bound: an oversized id fails as availability (a transport or model-context limit
18
+ // surfacing as a wave-level failure), never as a silent success or an injection — the ceiling is
19
+ // the transport's, not perk's. (contracts.md §8.35)
20
+ //
21
+ // The module has exactly two concerns: the routing-token FENCE above (`isRoutingToken` +
22
+ // `renderRoutingToken`) and the fixed ORCHESTRATION-KEY format (`orchestrationKey`). The
23
+ // vocabulary split (contracts.md §8.35): the SEMANTIC LANE ID is the producer-owned identity a
24
+ // typed outcome reports; a ROUTING TOKEN is that identity (or a pair-level token) rendered into
25
+ // task prose for byte-exact selection/echo; an ORCHESTRATION KEY is the opaque code-owned
26
+ // `runs.all` item key a producer-lane wave gives one lane — never derived from producer bytes,
27
+ // never surfaced as a lane identity.
28
+
29
+ /**
30
+ * The refused character classes as one reviewable single-line statement. No `g`/`u` flags: every
31
+ * member is a single BMP code unit, so UTF-16 iteration is exact and astral characters (two
32
+ * surrogate code units, neither inside any listed range) can never match.
33
+ */
34
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: refusing control characters is the point
35
+ const UNSAFE = /[\u0000-\u001f\u007f-\u009f\u2028\u2029"]/;
36
+
37
+ /**
38
+ * The fence predicate: `true` iff `token` is non-empty and carries none of the refused classes.
39
+ * Decoders and planners call this to refuse or degrade an unsafe id BEFORE a lane is planned.
40
+ */
41
+ export function isRoutingToken(token: string): boolean {
42
+ return token !== "" && !UNSAFE.test(token);
43
+ }
44
+
45
+ /**
46
+ * The asserting identity render helper for a `laneTask` interpolation site: returns `token`
47
+ * UNCHANGED when it passes the fence (the fenced form of an accepted token IS the raw token —
48
+ * every existing task-prose pin stays valid), otherwise throws. A throw here is a programmer
49
+ * error — a caller composed a task from a token it never fenced upstream — in the
50
+ * `validateAssignments` posture of `reportWave.ts`: never an operational failure arm, and
51
+ * unreachable on the production path (the strict decoders refuse, the audit planner degrades).
52
+ */
53
+ export function renderRoutingToken(token: string): string {
54
+ if (!isRoutingToken(token)) {
55
+ throw new Error(
56
+ `renderRoutingToken: routing token ${JSON.stringify(token)} failed the fence — refuse or degrade it upstream`,
57
+ );
58
+ }
59
+ return token;
60
+ }
61
+
62
+ /**
63
+ * The ONE fixed orchestration-key format the producer-lane learn waves — harvest, audit, and
64
+ * the dream analyst tier, whose lanes are drawn from a producer-owned manifest — use for their
65
+ * `runs.all` item keys: `lane.<ordinal>` — opaque, code-owned, never derived from producer
66
+ * bytes. (Waves over a closed slug enum — the learn analyst angles, the dream reducer angles —
67
+ * key by the slug itself and never need it.) There is deliberately NO sanitizer: uniqueness
68
+ * lives in the caller's ordinal, and the semantic lane id rides `label` and the task text.
69
+ * `ordinal` is the caller's global 1-based counter in lane-plan order. The format is trivially inside `RUN_KEY_PATTERN`, so `validateAssignments`' run-key
70
+ * throw is unreachable for any planned lane. The key is never surfaced as a lane identity — it
71
+ * appears only in attempt receipts (`requestedKeys`, `children[*].key`) and failure `detail`s;
72
+ * typed outcomes join rows back to the SEMANTIC id through each flow's module-private lane plan,
73
+ * never by parsing keys.
74
+ */
75
+ export function orchestrationKey(ordinal: number): string {
76
+ return `lane.${ordinal}`;
77
+ }
@@ -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
  }