@chllming/wave-orchestration 0.9.10 → 0.9.12

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 (42) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +7 -8
  3. package/docs/README.md +4 -10
  4. package/docs/architecture/README.md +12 -4
  5. package/docs/concepts/operating-modes.md +1 -1
  6. package/docs/guides/author-and-run-waves.md +3 -2
  7. package/docs/guides/planner.md +3 -3
  8. package/docs/guides/recommendations-0.9.11.md +44 -0
  9. package/docs/guides/recommendations-0.9.12.md +49 -0
  10. package/docs/guides/sandboxed-environments.md +2 -2
  11. package/docs/guides/terminal-surfaces.md +1 -1
  12. package/docs/plans/current-state.md +3 -3
  13. package/docs/plans/end-state-architecture.md +1 -1
  14. package/docs/plans/examples/wave-example-design-handoff.md +1 -1
  15. package/docs/plans/examples/wave-example-live-proof.md +1 -1
  16. package/docs/plans/migration.md +32 -28
  17. package/docs/plans/wave-orchestrator.md +1 -1
  18. package/docs/reference/cli-reference.md +2 -2
  19. package/docs/reference/coordination-and-closure.md +1 -1
  20. package/docs/reference/npmjs-token-publishing.md +2 -2
  21. package/docs/reference/package-publishing-flow.md +12 -12
  22. package/docs/reference/runtime-config/README.md +2 -2
  23. package/docs/reference/sample-waves.md +5 -5
  24. package/docs/reference/skills.md +1 -1
  25. package/docs/reference/wave-control.md +3 -1
  26. package/docs/roadmap.md +3 -3
  27. package/package.json +1 -1
  28. package/releases/manifest.json +32 -0
  29. package/scripts/wave-orchestrator/agent-state.mjs +3 -1
  30. package/scripts/wave-orchestrator/autonomous.mjs +2 -2
  31. package/scripts/wave-orchestrator/closure-engine.mjs +103 -6
  32. package/scripts/wave-orchestrator/closure-policy.mjs +319 -0
  33. package/scripts/wave-orchestrator/config.mjs +15 -0
  34. package/scripts/wave-orchestrator/derived-state-engine.mjs +52 -4
  35. package/scripts/wave-orchestrator/gate-engine.mjs +72 -4
  36. package/scripts/wave-orchestrator/install.mjs +1 -1
  37. package/scripts/wave-orchestrator/launcher.mjs +14 -4
  38. package/scripts/wave-orchestrator/planner.mjs +4 -3
  39. package/scripts/wave-orchestrator/shared.mjs +11 -12
  40. package/scripts/wave-orchestrator/swe-bench-pro-task.mjs +1 -1
  41. package/scripts/wave-orchestrator/traces.mjs +22 -1
  42. package/wave.config.json +13 -2
@@ -0,0 +1,319 @@
1
+ function normalizeBoolean(value, fallback = false) {
2
+ if (value === undefined || value === null || value === "") {
3
+ return fallback;
4
+ }
5
+ if (typeof value === "boolean") {
6
+ return value;
7
+ }
8
+ const normalized = String(value || "")
9
+ .trim()
10
+ .toLowerCase();
11
+ if (["true", "1", "yes", "on"].includes(normalized)) {
12
+ return true;
13
+ }
14
+ if (["false", "0", "no", "off"].includes(normalized)) {
15
+ return false;
16
+ }
17
+ return fallback;
18
+ }
19
+
20
+ function normalizeThreshold(value, fallback) {
21
+ if (value === null || value === undefined || value === "") {
22
+ return fallback;
23
+ }
24
+ const parsed = Number.parseInt(String(value), 10);
25
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
26
+ }
27
+
28
+ function contradictionList(value) {
29
+ if (!value) {
30
+ return [];
31
+ }
32
+ if (value instanceof Map) {
33
+ return Array.from(value.values());
34
+ }
35
+ if (Array.isArray(value)) {
36
+ return value;
37
+ }
38
+ if (typeof value === "object") {
39
+ return Object.values(value);
40
+ }
41
+ return [];
42
+ }
43
+
44
+ function openCoordinationRecords(records = []) {
45
+ return (Array.isArray(records) ? records : []).filter(
46
+ (record) =>
47
+ !["resolved", "closed", "cancelled", "superseded"].includes(
48
+ String(record?.status || "")
49
+ .trim()
50
+ .toLowerCase(),
51
+ ),
52
+ );
53
+ }
54
+
55
+ function closureSignalsFromDerivedState(derivedState = {}) {
56
+ const integrationSummary = derivedState?.integrationSummary || {};
57
+ const docsQueueItems = Array.isArray(derivedState?.docsQueue?.items)
58
+ ? derivedState.docsQueue.items
59
+ : [];
60
+ const coordinationState = derivedState?.coordinationState || {};
61
+ const clarificationBarrier = derivedState?.clarificationBarrier || { ok: true };
62
+ const helperAssignmentBarrier = derivedState?.helperAssignmentBarrier || { ok: true };
63
+ const dependencyBarrier = derivedState?.dependencyBarrier || { ok: true };
64
+ const securitySummary = derivedState?.securitySummary || null;
65
+ const corridorSummary =
66
+ derivedState?.corridorSummary || derivedState?.securitySummary?.corridor || null;
67
+ const blockingContradictions = contradictionList(derivedState?.contradictions).filter(
68
+ (entry) =>
69
+ ["blocking", "high"].includes(String(entry?.severity || "").trim().toLowerCase()) &&
70
+ ((Array.isArray(entry?.impactedGates) && entry.impactedGates.includes("integrationBarrier")) ||
71
+ !Array.isArray(entry?.impactedGates) ||
72
+ entry.impactedGates.length === 0) &&
73
+ !["resolved", "closed", "cleared"].includes(
74
+ String(entry?.status || "")
75
+ .trim()
76
+ .toLowerCase(),
77
+ ),
78
+ );
79
+ const openClarifications = openCoordinationRecords(coordinationState?.clarifications);
80
+ const openHuman = [
81
+ ...openCoordinationRecords(coordinationState?.humanEscalations),
82
+ ...openCoordinationRecords(coordinationState?.humanFeedback),
83
+ ];
84
+ const blockingAssignments = (Array.isArray(derivedState?.capabilityAssignments)
85
+ ? derivedState.capabilityAssignments
86
+ : []
87
+ ).filter((assignment) => assignment?.blocking !== false);
88
+ const openDependencies = [
89
+ ...((Array.isArray(derivedState?.dependencySnapshot?.openInbound)
90
+ ? derivedState.dependencySnapshot.openInbound
91
+ : []) || []),
92
+ ...((Array.isArray(derivedState?.dependencySnapshot?.openOutbound)
93
+ ? derivedState.dependencySnapshot.openOutbound
94
+ : []) || []),
95
+ ...((Array.isArray(derivedState?.dependencySnapshot?.unresolvedInboundAssignments)
96
+ ? derivedState.dependencySnapshot.unresolvedInboundAssignments
97
+ : []) || []),
98
+ ];
99
+ const sharedPlanItems = docsQueueItems.filter((item) => item?.kind === "shared-plan");
100
+ const componentMatrixItems = docsQueueItems.filter((item) => item?.kind === "component-matrix");
101
+ return {
102
+ integrationReady:
103
+ integrationSummary?.recommendation === "ready-for-doc-closure",
104
+ openClaims: Array.isArray(integrationSummary?.openClaims) ? integrationSummary.openClaims : [],
105
+ conflictingClaims: Array.isArray(integrationSummary?.conflictingClaims)
106
+ ? integrationSummary.conflictingClaims
107
+ : [],
108
+ unresolvedBlockers: Array.isArray(integrationSummary?.unresolvedBlockers)
109
+ ? integrationSummary.unresolvedBlockers
110
+ : [],
111
+ changedInterfaces: Array.isArray(integrationSummary?.changedInterfaces)
112
+ ? integrationSummary.changedInterfaces
113
+ : [],
114
+ crossComponentImpacts: Array.isArray(integrationSummary?.crossComponentImpacts)
115
+ ? integrationSummary.crossComponentImpacts
116
+ : [],
117
+ proofGaps: Array.isArray(integrationSummary?.proofGaps) ? integrationSummary.proofGaps : [],
118
+ docGaps: Array.isArray(integrationSummary?.docGaps) ? integrationSummary.docGaps : [],
119
+ deployRisks: Array.isArray(integrationSummary?.deployRisks)
120
+ ? integrationSummary.deployRisks
121
+ : [],
122
+ inboundDependencies: Array.isArray(integrationSummary?.inboundDependencies)
123
+ ? integrationSummary.inboundDependencies
124
+ : [],
125
+ outboundDependencies: Array.isArray(integrationSummary?.outboundDependencies)
126
+ ? integrationSummary.outboundDependencies
127
+ : [],
128
+ helperAssignments: Array.isArray(integrationSummary?.helperAssignments)
129
+ ? integrationSummary.helperAssignments
130
+ : [],
131
+ sharedPlanItems,
132
+ componentMatrixItems,
133
+ blockingContradictions,
134
+ openClarifications,
135
+ openHuman,
136
+ blockingAssignments,
137
+ openDependencies,
138
+ clarificationBarrier,
139
+ helperAssignmentBarrier,
140
+ dependencyBarrier,
141
+ securityState:
142
+ integrationSummary?.securityState || securitySummary?.overallState || "not-applicable",
143
+ corridorSummary,
144
+ };
145
+ }
146
+
147
+ export function resolveClosureMode(waveNumber, thresholds) {
148
+ if (!thresholds) {
149
+ return "strict";
150
+ }
151
+ const bootstrapStart = normalizeThreshold(thresholds.bootstrap, 0);
152
+ const standardStart = Math.max(
153
+ bootstrapStart,
154
+ normalizeThreshold(thresholds.standard, 4),
155
+ );
156
+ const strictStart = Math.max(
157
+ standardStart,
158
+ normalizeThreshold(thresholds.strict, 10),
159
+ );
160
+ if (waveNumber >= strictStart) {
161
+ return "strict";
162
+ }
163
+ if (waveNumber >= standardStart) {
164
+ return "standard";
165
+ }
166
+ return "bootstrap";
167
+ }
168
+
169
+ export function resolveClosurePolicyConfig(source = {}) {
170
+ const validation = source?.laneProfile?.validation || source?.validation || {};
171
+ const rawThresholds =
172
+ source?.closureModeThresholds || validation?.closureModeThresholds || null;
173
+ const rawAutoClosure = source?.autoClosure || validation?.autoClosure || {};
174
+ return {
175
+ closureModeThresholds: {
176
+ bootstrap: normalizeThreshold(rawThresholds?.bootstrap, 0),
177
+ standard: normalizeThreshold(rawThresholds?.standard, 4),
178
+ strict: normalizeThreshold(rawThresholds?.strict, 10),
179
+ },
180
+ autoClosure: {
181
+ allowInferredIntegration: normalizeBoolean(
182
+ rawAutoClosure?.allowInferredIntegration,
183
+ false,
184
+ ),
185
+ allowAutoDocNoChange: normalizeBoolean(
186
+ rawAutoClosure?.allowAutoDocNoChange,
187
+ false,
188
+ ),
189
+ allowAutoDocProjection: normalizeBoolean(
190
+ rawAutoClosure?.allowAutoDocProjection,
191
+ false,
192
+ ),
193
+ allowSkipContQaInBootstrap: normalizeBoolean(
194
+ rawAutoClosure?.allowSkipContQaInBootstrap,
195
+ false,
196
+ ),
197
+ },
198
+ };
199
+ }
200
+
201
+ export function classifyClosureComplexity(derivedState = {}) {
202
+ const signals = closureSignalsFromDerivedState(derivedState);
203
+ const hasStrictSignals =
204
+ signals.blockingContradictions.length > 0 ||
205
+ signals.openClarifications.length > 0 ||
206
+ signals.openHuman.length > 0 ||
207
+ signals.clarificationBarrier?.ok === false ||
208
+ signals.helperAssignmentBarrier?.ok === false ||
209
+ signals.dependencyBarrier?.ok === false ||
210
+ signals.securityState === "blocked" ||
211
+ signals.corridorSummary?.blocking === true ||
212
+ (signals.corridorSummary?.ok === false &&
213
+ signals.corridorSummary?.requiredAtClosure !== false);
214
+ if (hasStrictSignals) {
215
+ return "strict-full-closure";
216
+ }
217
+ const hasSemanticIntegrationSignals =
218
+ signals.openClaims.length > 0 ||
219
+ signals.conflictingClaims.length > 0 ||
220
+ signals.unresolvedBlockers.length > 0 ||
221
+ signals.changedInterfaces.length > 0 ||
222
+ signals.crossComponentImpacts.length > 0 ||
223
+ signals.proofGaps.length > 0 ||
224
+ signals.deployRisks.length > 0 ||
225
+ signals.helperAssignments.length > 0 ||
226
+ signals.inboundDependencies.length > 0 ||
227
+ signals.outboundDependencies.length > 0 ||
228
+ signals.blockingAssignments.length > 0 ||
229
+ signals.openDependencies.length > 0;
230
+ if (hasSemanticIntegrationSignals) {
231
+ return "semantic-integration";
232
+ }
233
+ if (signals.sharedPlanItems.length > 0) {
234
+ return "semantic-docs";
235
+ }
236
+ return "low-entropy";
237
+ }
238
+
239
+ export function evaluateInferredIntegrationClosure(derivedState = {}, source = {}) {
240
+ const policy = resolveClosurePolicyConfig(source);
241
+ if (!policy.autoClosure.allowInferredIntegration) {
242
+ return null;
243
+ }
244
+ const signals = closureSignalsFromDerivedState(derivedState);
245
+ if (!signals.integrationReady) {
246
+ return null;
247
+ }
248
+ const hasSemanticSignals =
249
+ signals.openClaims.length > 0 ||
250
+ signals.conflictingClaims.length > 0 ||
251
+ signals.unresolvedBlockers.length > 0 ||
252
+ signals.changedInterfaces.length > 0 ||
253
+ signals.crossComponentImpacts.length > 0 ||
254
+ signals.proofGaps.length > 0 ||
255
+ signals.deployRisks.length > 0 ||
256
+ signals.helperAssignments.length > 0 ||
257
+ signals.inboundDependencies.length > 0 ||
258
+ signals.outboundDependencies.length > 0 ||
259
+ signals.blockingContradictions.length > 0 ||
260
+ signals.openClarifications.length > 0 ||
261
+ signals.openHuman.length > 0 ||
262
+ signals.clarificationBarrier?.ok === false ||
263
+ signals.helperAssignmentBarrier?.ok === false ||
264
+ signals.dependencyBarrier?.ok === false ||
265
+ signals.blockingAssignments.length > 0 ||
266
+ signals.openDependencies.length > 0 ||
267
+ signals.securityState === "blocked" ||
268
+ signals.corridorSummary?.blocking === true ||
269
+ (signals.corridorSummary?.ok === false &&
270
+ signals.corridorSummary?.requiredAtClosure !== false);
271
+ if (hasSemanticSignals) {
272
+ return null;
273
+ }
274
+ return {
275
+ ok: true,
276
+ state: "inferred",
277
+ statusCode: "pass",
278
+ detail:
279
+ "Integration closure was inferred from derived state; no semantic integration contradictions or blockers remain.",
280
+ };
281
+ }
282
+
283
+ export function evaluateDocumentationAutoClosure(
284
+ derivedState = {},
285
+ source = {},
286
+ options = {},
287
+ ) {
288
+ const policy = resolveClosurePolicyConfig(source);
289
+ const signals = closureSignalsFromDerivedState(derivedState);
290
+ const componentMatrixGate = options.componentMatrixGate || { ok: true };
291
+ if (
292
+ policy.autoClosure.allowAutoDocNoChange &&
293
+ signals.sharedPlanItems.length === 0 &&
294
+ signals.componentMatrixItems.length === 0
295
+ ) {
296
+ return {
297
+ ok: true,
298
+ state: "no-change",
299
+ statusCode: "pass",
300
+ detail:
301
+ "Documentation closure was auto-satisfied because derived state shows no shared-plan or component-matrix delta.",
302
+ };
303
+ }
304
+ if (
305
+ policy.autoClosure.allowAutoDocProjection &&
306
+ signals.sharedPlanItems.length === 0 &&
307
+ signals.componentMatrixItems.length > 0 &&
308
+ componentMatrixGate.ok
309
+ ) {
310
+ return {
311
+ ok: true,
312
+ state: "auto-closed",
313
+ statusCode: "pass",
314
+ detail:
315
+ "Documentation closure was auto-satisfied because only mechanical component-matrix reconciliation remained and the canonical matrix is already current.",
316
+ };
317
+ }
318
+ return null;
319
+ }
@@ -536,6 +536,21 @@ function normalizeValidation(rawValidation = {}) {
536
536
  standard: rawValidation.gateModeThresholds?.standard ?? 4,
537
537
  strict: rawValidation.gateModeThresholds?.strict ?? 10,
538
538
  },
539
+ closureModeThresholds: {
540
+ bootstrap: rawValidation.closureModeThresholds?.bootstrap ?? 0,
541
+ standard: rawValidation.closureModeThresholds?.standard ?? 4,
542
+ strict: rawValidation.closureModeThresholds?.strict ?? 10,
543
+ },
544
+ autoClosure: {
545
+ allowInferredIntegration:
546
+ normalizeOptionalBoolean(rawValidation.autoClosure?.allowInferredIntegration, false),
547
+ allowAutoDocNoChange:
548
+ normalizeOptionalBoolean(rawValidation.autoClosure?.allowAutoDocNoChange, false),
549
+ allowAutoDocProjection:
550
+ normalizeOptionalBoolean(rawValidation.autoClosure?.allowAutoDocProjection, false),
551
+ allowSkipContQaInBootstrap:
552
+ normalizeOptionalBoolean(rawValidation.autoClosure?.allowSkipContQaInBootstrap, false),
553
+ },
539
554
  bootstrapPassConditions: {
540
555
  requireA0Verdict: rawValidation.bootstrapPassConditions?.requireA0Verdict ?? false,
541
556
  requireProofSignals: rawValidation.bootstrapPassConditions?.requireProofSignals ?? false,
@@ -3,7 +3,10 @@ import path from "node:path";
3
3
  import {
4
4
  materializeAgentExecutionSummaryForRun,
5
5
  materializeAgentExecutionSummaries,
6
+ readClarificationBarrier,
6
7
  readRunExecutionSummary,
8
+ readWaveAssignmentBarrier,
9
+ readWaveDependencyBarrier,
7
10
  } from "./gate-engine.mjs";
8
11
  import {
9
12
  isOpenCoordinationStatus,
@@ -51,6 +54,11 @@ import {
51
54
  readWaveCorridorContext,
52
55
  waveCorridorContextPath,
53
56
  } from "./corridor.mjs";
57
+ import {
58
+ classifyClosureComplexity,
59
+ resolveClosureMode,
60
+ resolveClosurePolicyConfig,
61
+ } from "./closure-policy.mjs";
54
62
 
55
63
  export function waveCoordinationLogPath(lanePaths, waveNumber) {
56
64
  return path.join(lanePaths.coordinationDir, `wave-${waveNumber}.jsonl`);
@@ -573,6 +581,11 @@ export function buildWaveIntegrationSummary({
573
581
  securitySummary,
574
582
  });
575
583
  if (explicitIntegration) {
584
+ // When the integration steward explicitly asserts ready-for-doc-closure,
585
+ // clear synthesized proof/doc gaps — the steward has signed off on them.
586
+ const stewardClearedGaps =
587
+ explicitIntegration.state === "ready-for-doc-closure" &&
588
+ (explicitIntegration.blockers || 0) === 0;
576
589
  return {
577
590
  wave: wave.wave,
578
591
  lane: lanePaths.lane,
@@ -595,8 +608,8 @@ export function buildWaveIntegrationSummary({
595
608
  ),
596
609
  changedInterfaces: evidence.changedInterfaces,
597
610
  crossComponentImpacts: evidence.crossComponentImpacts,
598
- proofGaps: evidence.proofGaps,
599
- docGaps: evidence.docGaps,
611
+ proofGaps: stewardClearedGaps ? [] : evidence.proofGaps,
612
+ docGaps: stewardClearedGaps ? [] : evidence.docGaps,
600
613
  deployRisks: evidence.deployRisks,
601
614
  securityState: evidence.securityState,
602
615
  securityFindings: evidence.securityFindings,
@@ -712,12 +725,13 @@ export function buildWaveDerivedState({
712
725
  componentPromotions: wave.componentPromotions,
713
726
  runtimeAssignments,
714
727
  });
728
+ const corridorSummary = readWaveCorridorContext(lanePaths, wave.wave);
715
729
  const securitySummary = buildWaveSecuritySummary({
716
730
  lanePaths,
717
731
  wave,
718
732
  attempt,
719
733
  summariesByAgentId,
720
- corridorSummary: readWaveCorridorContext(lanePaths, wave.wave),
734
+ corridorSummary,
721
735
  });
722
736
  const integrationSummary = buildWaveIntegrationSummary({
723
737
  lanePaths,
@@ -732,6 +746,35 @@ export function buildWaveDerivedState({
732
746
  dependencySnapshot,
733
747
  securitySummary,
734
748
  });
749
+ const closurePolicy = resolveClosurePolicyConfig(lanePaths);
750
+ const closureMode = resolveClosureMode(wave.wave, closurePolicy.closureModeThresholds);
751
+ const clarificationBarrier = readClarificationBarrier({
752
+ coordinationState,
753
+ });
754
+ const helperAssignmentBarrier = readWaveAssignmentBarrier(
755
+ {
756
+ capabilityAssignments,
757
+ },
758
+ {
759
+ gateMode: closureMode,
760
+ },
761
+ );
762
+ const dependencyBarrier = readWaveDependencyBarrier({
763
+ dependencySnapshot,
764
+ });
765
+ const closureComplexity = classifyClosureComplexity({
766
+ contradictions: coordinationState?.contradictions || [],
767
+ coordinationState,
768
+ docsQueue,
769
+ capabilityAssignments,
770
+ dependencySnapshot,
771
+ securitySummary,
772
+ corridorSummary,
773
+ integrationSummary,
774
+ clarificationBarrier,
775
+ helperAssignmentBarrier,
776
+ dependencyBarrier,
777
+ });
735
778
  const ledger = deriveWaveLedger({
736
779
  lane: lanePaths.lane,
737
780
  wave,
@@ -797,12 +840,17 @@ export function buildWaveDerivedState({
797
840
  dependencySnapshotMarkdownPath: waveDependencySnapshotMarkdownPath(lanePaths, wave.wave),
798
841
  securitySummary,
799
842
  securitySummaryPath: waveSecurityPath(lanePaths, wave.wave),
800
- corridorSummary: readWaveCorridorContext(lanePaths, wave.wave),
843
+ corridorSummary,
801
844
  corridorSummaryPath: waveCorridorContextPath(lanePaths, wave.wave),
802
845
  integrationSummary,
803
846
  integrationSummaryPath: waveIntegrationPath(lanePaths, wave.wave),
804
847
  integrationMarkdownPath: waveIntegrationMarkdownPath(lanePaths, wave.wave),
805
848
  securityMarkdownPath: waveSecurityMarkdownPath(lanePaths, wave.wave),
849
+ clarificationBarrier,
850
+ helperAssignmentBarrier,
851
+ dependencyBarrier,
852
+ closureMode,
853
+ closureComplexity,
806
854
  ledger,
807
855
  ledgerPath: waveLedgerPath(lanePaths, wave.wave),
808
856
  responseMetrics,
@@ -53,6 +53,10 @@ import {
53
53
  openClarificationLinkedRequests,
54
54
  } from "./coordination-store.mjs";
55
55
  import { contradictionsBlockingGate } from "./contradiction-entity.mjs";
56
+ import {
57
+ evaluateDocumentationAutoClosure,
58
+ evaluateInferredIntegrationClosure,
59
+ } from "./closure-policy.mjs";
56
60
 
57
61
  function contradictionList(value) {
58
62
  if (value instanceof Map) {
@@ -860,7 +864,21 @@ export function readWaveComponentMatrixGate(wave, agentRuns, options = {}) {
860
864
 
861
865
  export function readWaveDocumentationGate(wave, agentRuns, options = {}) {
862
866
  const mode = normalizeReadMode(options.mode || "live");
863
- const documentationAgentId = wave.documentationAgentId || "A9";
867
+ const documentationAgentId =
868
+ options.documentationAgentId || wave.documentationAgentId || "A9";
869
+ const autoClosure = evaluateDocumentationAutoClosure(options.derivedState, options, {
870
+ componentMatrixGate: options.componentMatrixGate,
871
+ });
872
+ if (autoClosure?.ok) {
873
+ return {
874
+ ok: true,
875
+ agentId: null,
876
+ statusCode: autoClosure.statusCode,
877
+ detail: autoClosure.detail,
878
+ logPath: null,
879
+ docClosureState: autoClosure.state,
880
+ };
881
+ }
864
882
  const docRun =
865
883
  agentRuns.find((run) => run.agent.agentId === documentationAgentId) ?? null;
866
884
  if (!docRun) {
@@ -1003,6 +1021,17 @@ export function readWaveIntegrationGate(wave, agentRuns, options = {}) {
1003
1021
  (options.requireIntegrationStewardFromWave !== null &&
1004
1022
  options.requireIntegrationStewardFromWave !== undefined &&
1005
1023
  wave.wave >= options.requireIntegrationStewardFromWave);
1024
+ const autoClosure = evaluateInferredIntegrationClosure(options.derivedState, options);
1025
+ if (autoClosure?.ok) {
1026
+ return {
1027
+ ok: true,
1028
+ agentId: null,
1029
+ statusCode: autoClosure.statusCode,
1030
+ detail: autoClosure.detail,
1031
+ logPath: null,
1032
+ integrationState: autoClosure.state,
1033
+ };
1034
+ }
1006
1035
  const integrationRun =
1007
1036
  agentRuns.find((run) => run.agent.agentId === integrationAgentId) ?? null;
1008
1037
  if (!integrationRun) {
@@ -1059,10 +1088,16 @@ export function readWaveIntegrationGate(wave, agentRuns, options = {}) {
1059
1088
  }
1060
1089
 
1061
1090
  export function readWaveIntegrationBarrier(wave, agentRuns, derivedState, options = {}) {
1062
- const markerGate = readWaveIntegrationGate(wave, agentRuns, options);
1091
+ const markerGate = readWaveIntegrationGate(wave, agentRuns, {
1092
+ ...options,
1093
+ derivedState,
1094
+ });
1063
1095
  if (!markerGate.ok) {
1064
1096
  return markerGate;
1065
1097
  }
1098
+ if (!markerGate.agentId) {
1099
+ return markerGate;
1100
+ }
1066
1101
  const integrationSummary = derivedState?.integrationSummary || null;
1067
1102
  if (!integrationSummary) {
1068
1103
  return {
@@ -1240,6 +1275,7 @@ export function buildGateSnapshot({
1240
1275
  securityRolePromptPath: lanePaths?.securityRolePromptPath,
1241
1276
  requireIntegrationStewardFromWave: lanePaths?.requireIntegrationStewardFromWave,
1242
1277
  laneProfile: lanePaths?.laneProfile,
1278
+ autoClosure: lanePaths?.autoClosure,
1243
1279
  benchmarkCatalogPath: lanePaths?.laneProfile?.paths?.benchmarkCatalogPath,
1244
1280
  componentMatrixPayload,
1245
1281
  componentMatrixJsonPath,
@@ -1446,6 +1482,19 @@ export function readWaveComponentMatrixGatePure(wave, agentResults, options = {}
1446
1482
 
1447
1483
  export function readWaveDocumentationGatePure(wave, agentResults, options = {}) {
1448
1484
  const documentationAgentId = options.documentationAgentId || wave.documentationAgentId || "A9";
1485
+ const autoClosure = evaluateDocumentationAutoClosure(options.derivedState, options, {
1486
+ componentMatrixGate: options.componentMatrixGate,
1487
+ });
1488
+ if (autoClosure?.ok) {
1489
+ return {
1490
+ ok: true,
1491
+ agentId: null,
1492
+ statusCode: autoClosure.statusCode,
1493
+ detail: autoClosure.detail,
1494
+ logPath: null,
1495
+ docClosureState: autoClosure.state,
1496
+ };
1497
+ }
1449
1498
  if (!waveDeclaresAgent(wave, documentationAgentId)) {
1450
1499
  return { ok: true, agentId: null, statusCode: "pass",
1451
1500
  detail: "No documentation steward declared for this wave.", logPath: null };
@@ -1507,6 +1556,17 @@ export function readWaveIntegrationGatePure(wave, agentResults, options = {}) {
1507
1556
  const integrationAgentId = options.integrationAgentId || wave.integrationAgentId || "A8";
1508
1557
  const requireIntegration = options.requireIntegrationSteward === true ||
1509
1558
  (options.requireIntegrationStewardFromWave != null && wave.wave >= options.requireIntegrationStewardFromWave);
1559
+ const autoClosure = evaluateInferredIntegrationClosure(options.derivedState, options);
1560
+ if (autoClosure?.ok) {
1561
+ return {
1562
+ ok: true,
1563
+ agentId: null,
1564
+ statusCode: autoClosure.statusCode,
1565
+ detail: autoClosure.detail,
1566
+ logPath: null,
1567
+ integrationState: autoClosure.state,
1568
+ };
1569
+ }
1510
1570
  if (!waveDeclaresAgent(wave, integrationAgentId)) {
1511
1571
  return {
1512
1572
  ok: !requireIntegration,
@@ -1557,6 +1617,9 @@ export function buildGateSnapshotPure({ wave, agentResults, derivedState, valida
1557
1617
  const integrationMarkerGate = readWaveIntegrationGatePure(wave, agentResults, {
1558
1618
  integrationAgentId: laneConfig.integrationAgentId,
1559
1619
  requireIntegrationStewardFromWave: laneConfig.requireIntegrationStewardFromWave,
1620
+ derivedState,
1621
+ laneProfile: laneConfig.laneProfile,
1622
+ autoClosure: laneConfig.autoClosure,
1560
1623
  });
1561
1624
  const integrationBarrier = (() => {
1562
1625
  if (!integrationMarkerGate.ok) { return integrationMarkerGate; }
@@ -1585,12 +1648,17 @@ export function buildGateSnapshotPure({ wave, agentResults, derivedState, valida
1585
1648
  }
1586
1649
  return integrationMarkerGate;
1587
1650
  })();
1588
- const documentationGate = readWaveDocumentationGatePure(wave, agentResults, {
1589
- documentationAgentId: laneConfig.documentationAgentId });
1590
1651
  const componentMatrixGate = readWaveComponentMatrixGatePure(wave, agentResults, {
1591
1652
  laneProfile: laneConfig.laneProfile, documentationAgentId: laneConfig.documentationAgentId,
1592
1653
  componentMatrixPayload: laneConfig.componentMatrixPayload,
1593
1654
  componentMatrixJsonPath: laneConfig.componentMatrixJsonPath });
1655
+ const documentationGate = readWaveDocumentationGatePure(wave, agentResults, {
1656
+ documentationAgentId: laneConfig.documentationAgentId,
1657
+ derivedState,
1658
+ laneProfile: laneConfig.laneProfile,
1659
+ autoClosure: laneConfig.autoClosure,
1660
+ componentMatrixGate,
1661
+ });
1594
1662
  const contEvalGate = readWaveContEvalGatePure(wave, agentResults, {
1595
1663
  contEvalAgentId: laneConfig.contEvalAgentId, mode: validationMode,
1596
1664
  evalTargets: wave.evalTargets, benchmarkCatalogPath: laneConfig.benchmarkCatalogPath });
@@ -69,7 +69,7 @@ export const STARTER_TEMPLATE_PATHS = [
69
69
  "docs/guides/author-and-run-waves.md",
70
70
  "docs/guides/monorepo-projects.md",
71
71
  "docs/guides/planner.md",
72
- "docs/guides/recommendations-0.9.7.md",
72
+ "docs/guides/recommendations-0.9.12.md",
73
73
  "docs/guides/sandboxed-environments.md",
74
74
  "docs/guides/signal-wrappers.md",
75
75
  "docs/guides/terminal-surfaces.md",
@@ -265,9 +265,9 @@ Options:
265
265
  --dry-run Parse waves and update manifest only
266
266
  --terminal-surface <mode>
267
267
  Terminal surface: ${TERMINAL_SURFACES.join(" | ")} (default: ${terminalSurface})
268
- --no-dashboard Disable per-wave tmux dashboard session
269
- --cleanup-sessions Kill lane tmux sessions after each wave (default: on)
270
- --keep-sessions Keep lane tmux sessions after each wave
268
+ --no-dashboard Disable the per-wave dashboard projection session
269
+ --cleanup-sessions Clean up lane tmux dashboard/projection sessions after each wave (default: on)
270
+ --keep-sessions Keep lane tmux dashboard/projection sessions after each wave
271
271
  --keep-terminals Do not remove temporary terminal entries after each wave
272
272
  --orchestrator-id <id> Stable orchestrator identity for cross-lane coordination
273
273
  --orchestrator-board <path>
@@ -477,6 +477,15 @@ function parseArgs(argv) {
477
477
  return { help: false, lanePaths, options, config };
478
478
  }
479
479
 
480
+ function maybePrintOptionalTmuxNote(options) {
481
+ if (options.dryRun || options.terminalSurface !== "tmux" || options.dashboard) {
482
+ return;
483
+ }
484
+ console.log(
485
+ "[terminal-surface] tmux is optional here: live agents still run as detached processes, and tmux only affects dashboard/projection attach when dashboards are enabled.",
486
+ );
487
+ }
488
+
480
489
  // --- Local wrappers that bind engine calls to launcher scope ---
481
490
 
482
491
  async function runClosureSweepPhase({
@@ -1062,11 +1071,12 @@ export async function runLauncherCli(argv) {
1062
1071
  console.log(
1063
1072
  `[dry-run] prompts and executor overlays written: ${path.relative(REPO_ROOT, lanePaths.executorOverlaysDir)}`,
1064
1073
  );
1065
- console.log("Dry run enabled, skipping tmux and executor launch.");
1074
+ console.log("Dry run enabled, skipping live execution and optional dashboard/projection launch.");
1066
1075
  return;
1067
1076
  }
1068
1077
 
1069
1078
  preflightWavesForExecutorAvailability(filteredWaves, lanePaths);
1079
+ maybePrintOptionalTmuxNote(options);
1070
1080
  const terminalRegistryEnabled = terminalSurfaceUsesTerminalRegistry(
1071
1081
  options.terminalSurface,
1072
1082
  );
@@ -2944,9 +2944,10 @@ async function runProjectSetupFlow(options = {}) {
2944
2944
  ),
2945
2945
  );
2946
2946
 
2947
- prompt.describe("\nHow do you want to watch agent sessions?");
2948
- prompt.describe(" vscode agent sessions appear as VS Code terminal tabs");
2949
- prompt.describe(" tmux agent sessions run in tmux panes (terminal-native)");
2947
+ prompt.describe("\nHow do you want to follow live runs?");
2948
+ prompt.describe("Live agent execution stays process-backed either way; this only changes the operator surface.");
2949
+ prompt.describe(" vscode VS Code gets temporary terminal entries for agent logs and dashboards");
2950
+ prompt.describe(" tmux — terminal-native dashboard and projection surface with no VS Code integration");
2950
2951
  const defaultTerminalSurface = normalizeTerminalSurface(
2951
2952
  await prompt.askChoice(
2952
2953
  "Default terminal surface",