@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
@@ -0,0 +1,435 @@
1
+ // The learn-dream analysis workflow as ONE typed feature operation — the two-level
2
+ // ordering/recovery POLICY sitting above the analyst tier (`dream.ts`) and the reducer tier
3
+ // (`dreamReducer.ts`). The sequence: the first-level analyst wave (strict) → the compact
4
+ // analyst bundle written under the enforced aggregate byte budget → the three fixed reducer
5
+ // lanes — reducers launch ONLY after a complete first wave and an in-budget write — then, only
6
+ // when BOTH waves completed, the revalidation bracket against the manifest's stamped
7
+ // `commit_sha` (drift skips the finalize AND the marker set — a drifted wave is structurally
8
+ // undraftable) and, bracket ok, the finalize-in-place rewrite of the same fixed name
9
+ // (`finalizeDreamBundle`, the added `reducers` section). Two writes of ONE name: the analyst
10
+ // write feeds the reducers; the finalize rewrite is what the dream-report recovery consumes.
11
+ // The `dream_bundle_digest` workflow-state marker is the recovery-side freshness authority:
12
+ // cleared unconditionally at entry BEFORE the stale-bundle removal attempt (the invalidation
13
+ // record — a failed cleanup leaves prior files behind, but recovery refuses them), set to the
14
+ // sha256 of the finalized bytes only after the finalize write succeeds.
15
+ //
16
+ // Pi-free by construction: the `ReportWave` seam and the function-shaped
17
+ // capabilities (`markBundleDigest`, `bracket`, `writeBundle`, `removeBundle`) are the only
18
+ // mechanism edges — the adapter constructs and threads them; artifact storage is never owned
19
+ // here. Analyst and reducer reports are untrusted DATA, re-decoded in code before they reach
20
+ // the caller. (contracts.md §8.61/§8.65)
21
+
22
+ import { dirname, join } from "node:path";
23
+ import type { ReportWave, ReportWaveAttemptReceipt } from "../waves/reportWave.ts";
24
+ import {
25
+ type DreamLaneAnalysis,
26
+ type DreamLaneFailure,
27
+ type DreamManifest,
28
+ runDreamAnalystWave,
29
+ } from "./dream.ts";
30
+ import {
31
+ composeDreamBundle,
32
+ DREAM_ANALYSES_FILENAME,
33
+ DREAM_BUNDLE_BUDGET_BYTES,
34
+ type DreamReducerAnalysis,
35
+ type DreamReducerFailure,
36
+ finalizeDreamBundle,
37
+ nonKeepProposals,
38
+ runDreamReducerWave,
39
+ } from "./dreamReducer.ts";
40
+
41
+ /** The first-level analysis section every aggregate arm carries. */
42
+ interface AnalysisSection<C extends boolean> {
43
+ complete: C;
44
+ analyses: DreamLaneAnalysis[];
45
+ failures: DreamLaneFailure[];
46
+ }
47
+
48
+ /** The reducer section on the two pre-launch skip arms. */
49
+ interface SkippedReducers<R extends "incomplete-analysis" | "budget-exceeded"> {
50
+ launched: false;
51
+ skip_reason: R;
52
+ complete: false;
53
+ reports: [];
54
+ failures: [];
55
+ }
56
+
57
+ /** The reducer section after a launch (failures only when the wave stayed incomplete — plus
58
+ * the synthetic `digest-marker` entry on the failed-marker-set arm). */
59
+ interface LaunchedReducers<C extends boolean, F extends DreamReducerFailure[] | []> {
60
+ launched: true;
61
+ skip_reason: null;
62
+ complete: C;
63
+ reports: DreamReducerAnalysis[];
64
+ failures: F;
65
+ }
66
+
67
+ /** The written-bundle accounting (the happy write: in budget, `overflow_bytes` pinned 0). */
68
+ interface WrittenBundle {
69
+ path: string;
70
+ written: true;
71
+ bytes: number;
72
+ budget_bytes: number;
73
+ overflow_bytes: 0;
74
+ }
75
+
76
+ /**
77
+ * The typed normalized aggregate — a discriminated union of the real post-launch arms over the
78
+ * exact wire fields (`complete`, `bundle` null/`written`, `bracket` null/`ok`, the
79
+ * `reducers.skip_reason` literals), each arm constructed at exactly one policy site, so
80
+ * contradictory combinations (e.g. `complete: true` with `bracket: null`) are unrepresentable
81
+ * while the serialized JSON stays byte-identical to the flat aggregate shape. Arms in policy
82
+ * order: incomplete first wave (no bundle, no bracket, no reducers) · over-budget (explicit
83
+ * accounting, nothing written — the loud corpus-growth tripwire, never truncation) ·
84
+ * incomplete reducer wave (the analyses-only bundle stays behind with a cleared marker) ·
85
+ * bracket drift (both waves done; finalize + marker set skipped — structurally undraftable) ·
86
+ * failed marker set (finalize landed; the unverified append is the synthetic `digest-marker`
87
+ * failure — honestly incomplete) · complete.
88
+ */
89
+ export type DreamAnalysisAggregate = { attempts: ReportWaveAttemptReceipt[] } & (
90
+ | {
91
+ complete: false;
92
+ analysis: AnalysisSection<false>;
93
+ bracket: null;
94
+ bundle: null;
95
+ reducers: SkippedReducers<"incomplete-analysis">;
96
+ }
97
+ | {
98
+ complete: false;
99
+ analysis: AnalysisSection<true>;
100
+ bracket: null;
101
+ bundle: {
102
+ path: string;
103
+ written: false;
104
+ bytes: number;
105
+ budget_bytes: number;
106
+ overflow_bytes: number;
107
+ };
108
+ reducers: SkippedReducers<"budget-exceeded">;
109
+ }
110
+ | {
111
+ complete: false;
112
+ analysis: AnalysisSection<true>;
113
+ bracket: null;
114
+ bundle: WrittenBundle;
115
+ reducers: LaunchedReducers<false, DreamReducerFailure[]>;
116
+ }
117
+ | {
118
+ complete: false;
119
+ analysis: AnalysisSection<true>;
120
+ bracket: { ok: false; detail: string | null };
121
+ bundle: WrittenBundle;
122
+ reducers: LaunchedReducers<true, []>;
123
+ }
124
+ | {
125
+ complete: false;
126
+ analysis: AnalysisSection<true>;
127
+ bracket: { ok: true; detail: string | null };
128
+ bundle: WrittenBundle;
129
+ reducers: LaunchedReducers<true, DreamReducerFailure[]>;
130
+ }
131
+ | {
132
+ complete: true;
133
+ analysis: AnalysisSection<true>;
134
+ bracket: { ok: true; detail: string | null };
135
+ bundle: WrittenBundle;
136
+ reducers: LaunchedReducers<true, []>;
137
+ }
138
+ );
139
+
140
+ /**
141
+ * The typed dream-analysis outcome: ONE `io_failed` arm for every io site — the unverified
142
+ * marker clear, a failed stale-bundle removal, a bundle-write throw, a finalize-write throw —
143
+ * retaining the analyst analyses AND every already-recorded attempt receipt (empty arrays
144
+ * pre-launch); or the post-launch `aggregate`.
145
+ */
146
+ export type DreamAnalysisOutcome =
147
+ | {
148
+ kind: "io_failed";
149
+ detail: string;
150
+ analyses: DreamLaneAnalysis[];
151
+ attempts: ReportWaveAttemptReceipt[];
152
+ }
153
+ | { kind: "aggregate"; details: DreamAnalysisAggregate };
154
+
155
+ /**
156
+ * The one dream-analysis entry op — the two-level ordering/recovery policy with every
157
+ * capability REQUIRED (the compiler walks every call site to an explicit choice; the adapter
158
+ * wires production seams, tests inject fakes). Caller preconditions: the manifest came from
159
+ * `decodeDreamManifest` and `verifyDocContainment` was run (the registered tool's pre-spawn
160
+ * ladder). The ordered sequence and each step's invariant are commented at their policy sites
161
+ * below: verified marker clear → entry-time bundle removal → the strict analyst wave → the
162
+ * budget check before reducer composition → the analyst-bundle write → the reducer wave → the
163
+ * §8.65 bracket (only after BOTH waves) → the finalize-in-place rewrite → the marker set.
164
+ */
165
+ export async function analyzeDream(
166
+ wave: ReportWave,
167
+ opts: {
168
+ manifest: DreamManifest;
169
+ /** The `sha256:<hex>` digest of the manifest BYTES the caller read + decoded — bound into
170
+ * the finalized bundle so recovery authenticates the manifest too. */
171
+ manifestDigest: string;
172
+ /** The one function-shaped `dream_bundle_digest` marker capability: `null` is the
173
+ * invalidation clear; a string is the FINALIZED BUNDLE BYTES, digested by the capability
174
+ * owner (the digest convention lives with the adapter, not the feature); the boolean is
175
+ * the verified append+read-back result. The adapter wires the production
176
+ * `appendWorkflowState` closure; tests inject fakes. */
177
+ markBundleDigest: (finalized: string | null) => boolean;
178
+ /** The post-wave revalidation bracket (contracts.md §8.65) — REQUIRED on purpose: every
179
+ * call site (production and tests) makes an explicit choice; production wires
180
+ * `revalidationBracket(ctx.cwd, manifest.commit_sha)`. */
181
+ bracket: () => { ok: boolean; detail: string | null };
182
+ writeBundle: (path: string, content: string) => void;
183
+ removeBundle: (path: string) => void;
184
+ analystModel?: string;
185
+ reducerModel?: string;
186
+ signal?: AbortSignal;
187
+ },
188
+ ): Promise<DreamAnalysisOutcome> {
189
+ // The invalidation record FIRST: any new attempt clears the digest marker before the removal
190
+ // attempt below, so a failed cleanup leaves prior files behind that recovery refuses. An
191
+ // UNVERIFIED clear refuses outright — with the old digest possibly still live, a failed
192
+ // removal below would leave the prior bundle+digest pair recoverable as fresh.
193
+ if (!opts.markBundleDigest(null)) {
194
+ return {
195
+ kind: "io_failed",
196
+ detail:
197
+ "dream_bundle_digest invalidation could not be verified — refusing to run the wave " +
198
+ "over possibly-recoverable prior finalized state",
199
+ analyses: [],
200
+ attempts: [],
201
+ };
202
+ }
203
+
204
+ // One path authority: the bundle lives beside the decode-time-bound manifest path — no
205
+ // second runScratchDir derivation inside this op. A failed removal refuses BEFORE any
206
+ // spawn (a typed io_failed, never an uncaught throw): launching over an irremovable stale
207
+ // bundle would break the current-attempt-only invariant.
208
+ const bundlePath = join(dirname(opts.manifest.manifestPath), DREAM_ANALYSES_FILENAME);
209
+ try {
210
+ opts.removeBundle(bundlePath);
211
+ } catch (error) {
212
+ const detail = error instanceof Error ? error.message : String(error);
213
+ return {
214
+ kind: "io_failed",
215
+ detail: `stale dream bundle removal failed at '${bundlePath}': ${detail}`,
216
+ analyses: [],
217
+ attempts: [],
218
+ };
219
+ }
220
+
221
+ const analysis = await runDreamAnalystWave(
222
+ wave,
223
+ {
224
+ manifest: opts.manifest,
225
+ ...(opts.analystModel !== undefined ? { model: opts.analystModel } : {}),
226
+ },
227
+ opts.signal,
228
+ );
229
+ const attempts = [analysis.attempt];
230
+
231
+ if (!analysis.complete) {
232
+ // STRICT: no bundle write, no reducer launch after an incomplete first wave.
233
+ return {
234
+ kind: "aggregate",
235
+ details: {
236
+ complete: false,
237
+ analysis: { complete: false, analyses: analysis.analyses, failures: analysis.failures },
238
+ bracket: null,
239
+ bundle: null,
240
+ reducers: {
241
+ launched: false,
242
+ skip_reason: "incomplete-analysis",
243
+ complete: false,
244
+ reports: [],
245
+ failures: [],
246
+ },
247
+ attempts,
248
+ },
249
+ };
250
+ }
251
+ const analysisDetails = {
252
+ complete: true as const,
253
+ analyses: analysis.analyses,
254
+ failures: analysis.failures,
255
+ };
256
+
257
+ const { content, bytes } = composeDreamBundle(opts.manifest, analysis.analyses);
258
+ if (bytes > DREAM_BUNDLE_BUDGET_BYTES) {
259
+ // The loud corpus-growth tripwire: explicit accounting, nothing written, no reducers —
260
+ // never truncation (enforced BEFORE reducer task composition).
261
+ return {
262
+ kind: "aggregate",
263
+ details: {
264
+ complete: false,
265
+ analysis: analysisDetails,
266
+ bracket: null,
267
+ bundle: {
268
+ path: bundlePath,
269
+ written: false,
270
+ bytes,
271
+ budget_bytes: DREAM_BUNDLE_BUDGET_BYTES,
272
+ overflow_bytes: bytes - DREAM_BUNDLE_BUDGET_BYTES,
273
+ },
274
+ reducers: {
275
+ launched: false,
276
+ skip_reason: "budget-exceeded",
277
+ complete: false,
278
+ reports: [],
279
+ failures: [],
280
+ },
281
+ attempts,
282
+ },
283
+ };
284
+ }
285
+
286
+ try {
287
+ opts.writeBundle(bundlePath, content);
288
+ } catch (error) {
289
+ const detail = error instanceof Error ? error.message : String(error);
290
+ return {
291
+ kind: "io_failed",
292
+ detail: `dream bundle write failed: ${detail}`,
293
+ analyses: analysis.analyses,
294
+ attempts,
295
+ };
296
+ }
297
+ const bundleDetails: WrittenBundle = {
298
+ path: bundlePath,
299
+ written: true,
300
+ bytes,
301
+ budget_bytes: DREAM_BUNDLE_BUDGET_BYTES,
302
+ overflow_bytes: 0,
303
+ };
304
+
305
+ const reducers = await runDreamReducerWave(
306
+ wave,
307
+ {
308
+ manifestPath: opts.manifest.manifestPath,
309
+ bundlePath,
310
+ proposals: nonKeepProposals(analysis.analyses),
311
+ ...(opts.reducerModel !== undefined ? { model: opts.reducerModel } : {}),
312
+ },
313
+ opts.signal,
314
+ );
315
+ attempts.push(reducers.attempt);
316
+
317
+ if (!reducers.complete) {
318
+ // An incomplete reducer wave leaves the analyses-only bundle and a cleared marker (the
319
+ // finalized decode refuses it); the bracket is never evaluated on an earlier arm.
320
+ return {
321
+ kind: "aggregate",
322
+ details: {
323
+ complete: false,
324
+ analysis: analysisDetails,
325
+ bracket: null,
326
+ bundle: bundleDetails,
327
+ reducers: {
328
+ launched: true,
329
+ skip_reason: null,
330
+ complete: false,
331
+ reports: reducers.reports,
332
+ failures: reducers.failures,
333
+ },
334
+ attempts,
335
+ },
336
+ };
337
+ }
338
+
339
+ // The post-wave revalidation bracket (§8.65): evaluated only after BOTH waves completed,
340
+ // BEFORE the finalize write. Drift skips the finalize AND the marker set — the entry
341
+ // clear stands, so recovery refuses the analyses-only bundle left behind (a drifted wave
342
+ // is structurally undraftable); the analyses + reducer reports stay in the aggregate for
343
+ // honest coverage reporting.
344
+ const bracket = opts.bracket();
345
+ if (!bracket.ok) {
346
+ return {
347
+ kind: "aggregate",
348
+ details: {
349
+ complete: false,
350
+ analysis: analysisDetails,
351
+ bracket: { ok: false, detail: bracket.detail },
352
+ bundle: bundleDetails,
353
+ reducers: {
354
+ launched: true,
355
+ skip_reason: null,
356
+ complete: true,
357
+ reports: reducers.reports,
358
+ failures: [],
359
+ },
360
+ attempts,
361
+ },
362
+ };
363
+ }
364
+
365
+ // Finalize in place — the SAME fixed name gains the reducers section (never a second
366
+ // file), then the digest marker publishes the finalized bytes for the recovery consumer.
367
+ // An incomplete reducer wave never reaches here: the analyses-only shape stays behind with
368
+ // a cleared marker, and the finalized decode refuses it.
369
+ const finalized = finalizeDreamBundle(
370
+ opts.manifest,
371
+ analysis.analyses,
372
+ reducers.reports,
373
+ opts.manifestDigest,
374
+ );
375
+ try {
376
+ opts.writeBundle(bundlePath, finalized);
377
+ } catch (error) {
378
+ const detail = error instanceof Error ? error.message : String(error);
379
+ return {
380
+ kind: "io_failed",
381
+ detail: `dream bundle finalize write failed: ${detail}`,
382
+ analyses: analysis.analyses,
383
+ attempts,
384
+ };
385
+ }
386
+
387
+ // A failed marker append (an unverified read-back) leaves the marker cleared by the
388
+ // entry clear — recovery refuses (fail-closed) — and the aggregate reports the outcome
389
+ // as honestly incomplete; re-running the wave repairs it.
390
+ if (!opts.markBundleDigest(finalized)) {
391
+ return {
392
+ kind: "aggregate",
393
+ details: {
394
+ complete: false,
395
+ analysis: analysisDetails,
396
+ bracket: { ok: true, detail: bracket.detail },
397
+ bundle: bundleDetails,
398
+ reducers: {
399
+ launched: true,
400
+ skip_reason: null,
401
+ complete: true,
402
+ reports: reducers.reports,
403
+ failures: [
404
+ {
405
+ angle: "digest-marker",
406
+ reason: "run-failed",
407
+ detail:
408
+ "dream_bundle_digest marker append failed its read-back — the marker stays " +
409
+ "cleared, so recovery refuses this bundle; re-run perk learn dream",
410
+ },
411
+ ],
412
+ },
413
+ attempts,
414
+ },
415
+ };
416
+ }
417
+
418
+ return {
419
+ kind: "aggregate",
420
+ details: {
421
+ complete: true,
422
+ analysis: analysisDetails,
423
+ bracket: { ok: true, detail: bracket.detail },
424
+ bundle: bundleDetails,
425
+ reducers: {
426
+ launched: true,
427
+ skip_reason: null,
428
+ complete: true,
429
+ reports: reducers.reports,
430
+ failures: [],
431
+ },
432
+ attempts,
433
+ },
434
+ };
435
+ }
@@ -1,20 +1,27 @@
1
- // The learn-dream reducer wave the second-level entrypoint over the shared report-wave
2
- // runner: three FIXED fresh-context `perk.dream-reducer` lanes (`DREAM_REDUCER_ANGLES`) that
1
+ // The learn-dream reducer tier over the shared report-wave module: three FIXED fresh-context
2
+ // `perk.dream-reducer` lanes (`DREAM_REDUCER_ANGLES`) that
3
3
  // cross-examine the complete first-level analyst outcome. Pure orchestration — this module
4
- // composes the bundle content and the reducer lanes but performs NO fs writes (the door owns
5
- // the bundle writes). It owns the bundle serialization (`composeDreamBundle` — the compact
6
- // analyst reports beside the run's manifest, under the aggregate byte budget the door
4
+ // composes the bundle content and the reducer lanes but performs NO fs writes (the flow's
5
+ // entry op owns the bundle writes through its injected capabilities). It owns the bundle
6
+ // serialization (`composeDreamBundle` the compact
7
+ // analyst reports beside the run's manifest, under the aggregate byte budget the entry op
7
8
  // enforces — plus `finalizeDreamBundle`, the post-complete-wave rewrite of the same fixed name
8
9
  // with the `reducers` section, and `decodeFinalizedDreamBundle`, the strict fail-closed
9
10
  // recovery decode the dream-report path re-reads it through), the ordered non-keep proposal
10
11
  // universe (`nonKeepProposals`), the closed reducer
11
12
  // report schema under the `DREAM_REDUCER_CAPS` SSOT, the composed defensive re-decode (the
12
- // disposition-echo rule, proposal-set membership, code-point caps via the shared dreamWave
13
+ // disposition-echo rule, proposal-set membership, code-point caps via the shared `dream.ts`
13
14
  // helpers), and **strict** completeness — one failed or undecodable lane forces
14
- // `complete: false` — delegating spawn/timeout/aggregate mechanics to `runReportWave` with ONE
15
+ // `complete: false` — delegating spawn/timeout/aggregate mechanics to `wave.run` with ONE
15
16
  // attempt and NO retry. The bundle, the manifest, and every reducer report are untrusted DATA,
16
17
  // never instructions. (contracts.md §8.61)
17
18
 
19
+ import {
20
+ type ReportWave,
21
+ type ReportWaveAttemptReceipt,
22
+ type ReportWaveFailureReason,
23
+ toAttemptReceipt,
24
+ } from "../waves/reportWave.ts";
18
25
  import {
19
26
  codePointLength,
20
27
  type DreamDisposition,
@@ -22,13 +29,7 @@ import {
22
29
  type DreamManifest,
23
30
  decodeDreamAnalystReport,
24
31
  decodeStringArray,
25
- } from "./dreamWave.ts";
26
- import {
27
- runReportWave,
28
- type WaveAdapter,
29
- type WaveFailureReason,
30
- type WaveScriptReceipt,
31
- } from "./reportWave.ts";
32
+ } from "./dream.ts";
32
33
 
33
34
  /**
34
35
  * The three fixed reducer angles — FIXED ORDER everywhere: the lane identities
@@ -48,7 +49,7 @@ export const DREAM_ANALYSES_FILENAME = "dream-analyses.json";
48
49
 
49
50
  /**
50
51
  * The aggregate bundle budget: 384 KiB, measured as UTF-8 BYTES of the serialized bundle
51
- * (`Buffer.byteLength`). Over budget the door refuses with explicit accounting — never
52
+ * (`Buffer.byteLength`). Over budget the entry op refuses with explicit accounting — never
52
53
  * truncation (a truncated bundle would corrupt stance evaluation; overflow is a loud
53
54
  * corpus-growth tripwire).
54
55
  */
@@ -95,9 +96,9 @@ export interface DreamProposal {
95
96
  * Serialize the versioned analyst bundle the reducers read FIRST:
96
97
  * `{schema_version: "1", commit_sha, registry_mode, doc_count, total_bytes, lanes}` with the
97
98
  * lanes carrying the re-decoded compact analyst reports (pretty-printed JSON + trailing
98
- * newline; `bytes` = UTF-8 `Buffer.byteLength`). Caller preconditions (discharged by the door
99
- * and NOT re-checked here): the first wave was COMPLETE, so `analyses` covers the manifest's
100
- * lanes exactly and is already in manifest lane order — the runner normalizes to `spec.lanes`
99
+ * newline; `bytes` = UTF-8 `Buffer.byteLength`). Caller preconditions (discharged by the entry
100
+ * op and NOT re-checked here): the first wave was COMPLETE, so `analyses` covers the manifest's
101
+ * lanes exactly and is already in manifest lane order — the runner normalizes to `spec.assignments`
101
102
  * order, `buildDreamLanes` plans in manifest order, and `decodeDreamAnalystReport` normalizes
102
103
  * each report's docs to manifest lane-doc order, so no re-sort layer exists here.
103
104
  */
@@ -122,7 +123,8 @@ export function composeDreamBundle(
122
123
  * two-level wave (contracts.md §8.61): the `composeDreamBundle` wrapper fields unchanged
123
124
  * (`schema_version` stays `"1"`) plus `manifest_digest` — the `sha256:<hex>` digest of the
124
125
  * on-disk manifest BYTES this wave decoded (`analyses`/`reducers` must be in manifest lane /
125
- * fixed `DREAM_REDUCER_ANGLES` order — guaranteed by the wave outcome shapes the door passes) —
126
+ * fixed `DREAM_REDUCER_ANGLES` order — guaranteed by the wave outcome shapes the entry op
127
+ * passes) —
126
128
  * binding the manifest into the authenticated chain (the `dream_bundle_digest` marker
127
129
  * authenticates these bundle bytes; this field extends that authority to the manifest, so an
128
130
  * at-rest manifest edit that preserves the echoed identity fields still refuses at recovery) —
@@ -255,23 +257,23 @@ export interface DreamReducerAnalysis {
255
257
  /**
256
258
  * One reducer failure — the `DreamLaneFailure` shape with ANGLE identity (a thin
257
259
  * dream-specific remap so the aggregate's failure vocabulary stays angle-named, never
258
- * runner-key-named): `angle` is the assigned angle slug, or `null` for wave-level failures.
260
+ * wave-key-named): `angle` is the assigned angle slug, or `null` for wave-level failures.
259
261
  */
260
262
  export interface DreamReducerFailure {
261
263
  angle: string | null;
262
- reason: WaveFailureReason;
264
+ reason: ReportWaveFailureReason;
263
265
  detail: string;
264
266
  }
265
267
 
266
- /** The typed reducer outcome: strict completeness with reports RETAINED even when incomplete. */
268
+ /** The typed reducer outcome: strict completeness with reports RETAINED even when incomplete.
269
+ * `attempt` is the launch's flow-attributed output-free receipt (observability only), composed
270
+ * at the seam — its `requestedKeys` are the code-owned orchestration keys in launch order (=
271
+ * the angle slugs), receipt-correlation telemetry only (the `DreamWaveOutcome.attempt` twin). */
267
272
  export interface DreamReducerOutcome {
268
273
  complete: boolean;
269
274
  reports: DreamReducerAnalysis[];
270
275
  failures: DreamReducerFailure[];
271
- receipt: WaveScriptReceipt;
272
- /** The code-owned orchestration keys in launch order (= the angle slugs) —
273
- * receipt-correlation telemetry only (the `DreamWaveOutcome.requestedKeys` twin). */
274
- requestedKeys: string[];
276
+ attempt: ReportWaveAttemptReceipt;
275
277
  }
276
278
 
277
279
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -296,8 +298,11 @@ function nonNegativeInteger(value: unknown): value is number {
296
298
  * - stances normalized to the `proposals` order (deterministic downstream);
297
299
  * - EMPTY `stances` is valid — the re-decode never requires stance coverage: silence counts
298
300
  * as non-endorsement downstream (the dream-report node's evidence bar, not this decode).
301
+ *
302
+ * Module-private: both callers (`runDreamReducerWave`, `decodeFinalizedDreamBundle`) live
303
+ * here — its refusal matrix is exercised through them.
299
304
  */
300
- export function decodeDreamReducerReport(
305
+ function decodeDreamReducerReport(
301
306
  report: unknown,
302
307
  angle: string,
303
308
  proposals: readonly DreamProposal[],
@@ -605,7 +610,7 @@ export function decodeFinalizedDreamBundle(
605
610
 
606
611
  /**
607
612
  * Compose one reducer lane's task text IN CODE (short — the judgment rubric lives in the agent
608
- * def, the `dreamWave.ts` `laneTask` posture): the assigned angle, the bundle path (read
613
+ * def, the `dream.ts` `laneTask` posture): the assigned angle, the bundle path (read
609
614
  * FIRST), and the manifest path (doc identity, cluster rollups, findings).
610
615
  */
611
616
  function reducerTask(angle: string, bundlePath: string, manifestPath: string): string {
@@ -623,19 +628,19 @@ function reducerTask(angle: string, bundlePath: string, manifestPath: string): s
623
628
  * Run the dream reducer wave: three fixed fresh-context `perk.dream-reducer` lanes — key =
624
629
  * label = the angle slug (code-owned, run-key-safe by construction) — under **strict**
625
630
  * completeness, ONE attempt, NO retry, module-default timeout, the caller's `model?` as the
626
- * workflow-level default (`[models.subagents] dream-reducer`, resolved by the door at execute
627
- * time). Every schema-valid report is defensively re-decoded (`decodeDreamReducerReport`)
631
+ * workflow-level default (`[models.subagents] dream-reducer`, resolved by the adapter at
632
+ * execute time). Every schema-valid report is defensively re-decoded (`decodeDreamReducerReport`)
628
633
  * against its assigned angle and the ordered non-keep proposal universe — a decode miss is a
629
- * `malformed-report` failure carrying the angle identity; `complete` = the runner's
634
+ * `malformed-report` failure carrying the angle identity; `complete` = the wave's
630
635
  * completeness AND zero decode failures, with decoded reports retained even when incomplete
631
636
  * and normalized to `DREAM_REDUCER_ANGLES` order.
632
637
  *
633
- * Caller preconditions (discharged by the launching door): the first-level analyst wave was
634
- * COMPLETE, the bundle at `bundlePath` was written by the current call, and `proposals` is
638
+ * Caller preconditions (discharged by the launching entry op): the first-level analyst wave
639
+ * was COMPLETE, the bundle at `bundlePath` was written by the current call, and `proposals` is
635
640
  * `nonKeepProposals` over the complete analyses.
636
641
  */
637
642
  export async function runDreamReducerWave(
638
- adapter: WaveAdapter,
643
+ wave: ReportWave,
639
644
  opts: {
640
645
  manifestPath: string;
641
646
  bundlePath: string;
@@ -645,11 +650,10 @@ export async function runDreamReducerWave(
645
650
  signal?: AbortSignal,
646
651
  ): Promise<DreamReducerOutcome> {
647
652
  const requestedKeys = [...DREAM_REDUCER_ANGLES];
648
- const result = await runReportWave(
649
- adapter,
653
+ const result = await wave.run(
650
654
  {
651
655
  flow: "dream-reducer",
652
- lanes: DREAM_REDUCER_ANGLES.map((angle) => ({
656
+ assignments: DREAM_REDUCER_ANGLES.map((angle) => ({
653
657
  key: angle,
654
658
  label: angle,
655
659
  agent: "perk.dream-reducer",
@@ -660,10 +664,10 @@ export async function runDreamReducerWave(
660
664
  completeness: "strict",
661
665
  ...(opts.model !== undefined ? { model: opts.model } : {}),
662
666
  },
663
- signal,
667
+ { signal },
664
668
  );
665
669
 
666
- // The lane keys ARE the angle slugs (code-owned, fixed), and the runner normalizes strictly
670
+ // The lane keys ARE the angle slugs (code-owned, fixed), and the wave normalizes strictly
667
671
  // against them — a keyed report/failure carries an angle by construction; wave-level failures
668
672
  // carry `angle: null`.
669
673
  const failures: DreamReducerFailure[] = result.failures.map((failure) => ({
@@ -694,7 +698,8 @@ export async function runDreamReducerWave(
694
698
  complete: result.complete && decodeFailures === 0,
695
699
  reports,
696
700
  failures,
697
- receipt: result.receipt,
698
- requestedKeys,
701
+ // The transport receipt converts at this seam: ONE attempt, the fixed angle slugs as the
702
+ // pre-launch assignment manifest.
703
+ attempt: toAttemptReceipt("dream-reducer", 1, requestedKeys, result.receipt),
699
704
  };
700
705
  }