ai-maestro 1.0.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 (81) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +15 -0
  3. package/README.md +727 -0
  4. package/bin/maestro.mjs +246 -0
  5. package/package.json +29 -0
  6. package/src/checkpoint.mjs +102 -0
  7. package/src/codex-diagnostics.mjs +682 -0
  8. package/src/codex-home-inspect.mjs +252 -0
  9. package/src/codex-home.mjs +258 -0
  10. package/src/commands.mjs +809 -0
  11. package/src/config.mjs +11 -0
  12. package/src/debug.mjs +164 -0
  13. package/src/encoding-guard.mjs +125 -0
  14. package/src/engines/ai-router-engine.mjs +37 -0
  15. package/src/engines/codex-engine.mjs +21 -0
  16. package/src/engines/engine.mjs +21 -0
  17. package/src/engines/registry.mjs +29 -0
  18. package/src/files.mjs +65 -0
  19. package/src/format.mjs +132 -0
  20. package/src/lock.mjs +439 -0
  21. package/src/memory-log.mjs +18 -0
  22. package/src/memory.mjs +1 -0
  23. package/src/orchestration/attempt-chain-runtime.mjs +627 -0
  24. package/src/orchestration/budget-manager.mjs +121 -0
  25. package/src/orchestration/capability-registry.mjs +108 -0
  26. package/src/orchestration/consolidator.mjs +772 -0
  27. package/src/orchestration/delegation-executor.mjs +262 -0
  28. package/src/orchestration/delegation-manager.mjs +116 -0
  29. package/src/orchestration/deployment-intent.mjs +36 -0
  30. package/src/orchestration/engine-history.mjs +110 -0
  31. package/src/orchestration/engine-policy.mjs +73 -0
  32. package/src/orchestration/engine-selector.mjs +187 -0
  33. package/src/orchestration/execution-context.mjs +45 -0
  34. package/src/orchestration/failure-classifier.mjs +136 -0
  35. package/src/orchestration/failure-evidence.mjs +237 -0
  36. package/src/orchestration/fallback-chain-lock.mjs +217 -0
  37. package/src/orchestration/fallback-chain-trail.mjs +218 -0
  38. package/src/orchestration/fallback-executor.mjs +146 -0
  39. package/src/orchestration/fallback-graph.mjs +106 -0
  40. package/src/orchestration/fallback-recommendation.mjs +73 -0
  41. package/src/orchestration/file-conflict-detector.mjs +126 -0
  42. package/src/orchestration/host-validation.mjs +241 -0
  43. package/src/orchestration/orchestration-loop.mjs +1971 -0
  44. package/src/orchestration/orchestration-runtime.mjs +1019 -0
  45. package/src/orchestration/orchestration-scheduler.mjs +135 -0
  46. package/src/orchestration/orchestration-trail.mjs +192 -0
  47. package/src/orchestration/preflight.mjs +212 -0
  48. package/src/orchestration/provider-health.mjs +566 -0
  49. package/src/orchestration/provider-router.mjs +352 -0
  50. package/src/orchestration/rc-check-adapters.mjs +817 -0
  51. package/src/orchestration/rc-check.mjs +544 -0
  52. package/src/orchestration/rc-policy.mjs +200 -0
  53. package/src/orchestration/runtime-gate.mjs +146 -0
  54. package/src/orchestration/sector-managers.mjs +215 -0
  55. package/src/orchestration/self-hosting-canary.mjs +231 -0
  56. package/src/orchestration/self-hosting-readiness.mjs +244 -0
  57. package/src/orchestration/spec-planner.mjs +877 -0
  58. package/src/orchestration/subtask-planner.mjs +176 -0
  59. package/src/orchestration/task-classifier.mjs +241 -0
  60. package/src/orchestration/verifier.mjs +1608 -0
  61. package/src/orchestration-commands.mjs +279 -0
  62. package/src/planner.mjs +38 -0
  63. package/src/project.mjs +1 -0
  64. package/src/run-diagnostics.mjs +641 -0
  65. package/src/runner.mjs +243 -0
  66. package/src/safety.mjs +116 -0
  67. package/src/schema.mjs +371 -0
  68. package/src/session-commands.mjs +521 -0
  69. package/src/shell.mjs +93 -0
  70. package/src/smart-planner.mjs +249 -0
  71. package/src/task-commands.mjs +1182 -0
  72. package/src/tasks.mjs +134 -0
  73. package/src/ui/commands.mjs +76 -0
  74. package/src/ui/events.mjs +45 -0
  75. package/src/ui/public/app.js +600 -0
  76. package/src/ui/public/index.html +88 -0
  77. package/src/ui/public/style.css +460 -0
  78. package/src/ui/server.mjs +112 -0
  79. package/src/ui/state.mjs +504 -0
  80. package/src/usage.mjs +178 -0
  81. package/src/workspace-diff.mjs +228 -0
@@ -0,0 +1,1971 @@
1
+ const SCHEMA_VERSION = 1;
2
+
3
+ const LOOP_STATES = new Set([
4
+ 'loop.initialized',
5
+ 'loop.input_validated',
6
+ 'loop.classified',
7
+ 'loop.manager_selected',
8
+ 'loop.engine_candidate_selected',
9
+ 'loop.preflight_evaluated',
10
+ 'loop.budget_evaluated',
11
+ 'loop.policy_evaluated',
12
+ 'loop.runtime_gate_evaluated',
13
+ 'loop.execution_planned',
14
+ 'loop.delegation_planned',
15
+ 'loop.subtasks_planned',
16
+ 'loop.conflicts_evaluated',
17
+ 'loop.execution_simulated',
18
+ 'loop.results_consolidated',
19
+ 'loop.result_verified',
20
+ 'loop.completed',
21
+ 'loop.failed',
22
+ 'loop.blocked',
23
+ 'loop.needs_human',
24
+ 'loop.cancelled_limit',
25
+ 'loop.cancelled_cycle',
26
+ 'loop.cancelled_invalid_transition',
27
+ 'loop.cancelled_invalid_input'
28
+ ]);
29
+
30
+ const TERMINAL_STATES = new Set([
31
+ 'loop.completed',
32
+ 'loop.failed',
33
+ 'loop.blocked',
34
+ 'loop.needs_human',
35
+ 'loop.cancelled_limit',
36
+ 'loop.cancelled_cycle',
37
+ 'loop.cancelled_invalid_transition',
38
+ 'loop.cancelled_invalid_input'
39
+ ]);
40
+
41
+ const EVENTS = new Set([
42
+ 'START',
43
+ 'INPUT_VALID',
44
+ 'INPUT_INVALID',
45
+ 'CLASSIFICATION_READY',
46
+ 'CLASSIFICATION_INVALID',
47
+ 'MANAGER_READY',
48
+ 'MANAGER_INVALID',
49
+ 'ENGINE_CANDIDATE_READY',
50
+ 'ENGINE_CANDIDATE_INVALID',
51
+ 'PREFLIGHT_READY',
52
+ 'PREFLIGHT_INVALID',
53
+ 'BUDGET_READY',
54
+ 'BUDGET_INVALID',
55
+ 'POLICY_READY',
56
+ 'POLICY_INVALID',
57
+ 'RUNTIME_GATE_READY',
58
+ 'GATE_EXECUTE',
59
+ 'GATE_BLOCK',
60
+ 'GATE_NEEDS_HUMAN',
61
+ 'GATE_DELEGATE',
62
+ 'GATE_FALLBACK',
63
+ 'GATE_UNKNOWN',
64
+ 'PLAN_READY',
65
+ 'DELEGATION_READY',
66
+ 'DELEGATION_INVALID',
67
+ 'SUBTASKS_READY',
68
+ 'SUBTASKS_INVALID',
69
+ 'CONFLICTS_CLEAR',
70
+ 'CONFLICTS_FOUND',
71
+ 'CONFLICTS_BLOCKING',
72
+ 'CONFLICTS_INVALID',
73
+ 'SIMULATION_READY',
74
+ 'CONSOLIDATION_READY',
75
+ 'CONSOLIDATION_SKIPPED',
76
+ 'CONSOLIDATION_INVALID',
77
+ 'VERIFICATION_READY',
78
+ 'VERIFICATION_SKIPPED',
79
+ 'VERIFICATION_INVALID',
80
+ 'LIMIT_EXCEEDED',
81
+ 'CYCLE_DETECTED',
82
+ 'INVALID_TRANSITION',
83
+ 'TERMINAL_STATE'
84
+ ]);
85
+
86
+ const TRANSITIONS = {
87
+ 'loop.initialized': {
88
+ INPUT_VALID: 'loop.input_validated',
89
+ INPUT_INVALID: 'loop.cancelled_invalid_input'
90
+ },
91
+ 'loop.input_validated': {
92
+ CLASSIFICATION_READY: 'loop.classified',
93
+ CLASSIFICATION_INVALID: 'loop.failed'
94
+ },
95
+ 'loop.classified': {
96
+ MANAGER_READY: 'loop.manager_selected',
97
+ MANAGER_INVALID: 'loop.failed'
98
+ },
99
+ 'loop.manager_selected': {
100
+ ENGINE_CANDIDATE_READY: 'loop.engine_candidate_selected',
101
+ ENGINE_CANDIDATE_INVALID: 'loop.failed'
102
+ },
103
+ 'loop.engine_candidate_selected': {
104
+ PREFLIGHT_READY: 'loop.preflight_evaluated',
105
+ PREFLIGHT_INVALID: 'loop.failed'
106
+ },
107
+ 'loop.preflight_evaluated': {
108
+ BUDGET_READY: 'loop.budget_evaluated',
109
+ BUDGET_INVALID: 'loop.failed'
110
+ },
111
+ 'loop.budget_evaluated': {
112
+ POLICY_READY: 'loop.policy_evaluated',
113
+ POLICY_INVALID: 'loop.failed'
114
+ },
115
+ 'loop.policy_evaluated': {
116
+ RUNTIME_GATE_READY: 'loop.runtime_gate_evaluated',
117
+ GATE_UNKNOWN: 'loop.failed'
118
+ },
119
+ 'loop.runtime_gate_evaluated': {
120
+ GATE_EXECUTE: 'loop.execution_planned',
121
+ GATE_DELEGATE: 'loop.execution_planned',
122
+ GATE_BLOCK: 'loop.blocked',
123
+ GATE_NEEDS_HUMAN: 'loop.needs_human',
124
+ GATE_FALLBACK: 'loop.blocked'
125
+ },
126
+ 'loop.execution_planned': {
127
+ PLAN_READY: 'loop.conflicts_evaluated',
128
+ DELEGATION_READY: 'loop.delegation_planned',
129
+ DELEGATION_INVALID: 'loop.failed'
130
+ },
131
+ 'loop.delegation_planned': {
132
+ SUBTASKS_READY: 'loop.subtasks_planned',
133
+ SUBTASKS_INVALID: 'loop.failed'
134
+ },
135
+ 'loop.subtasks_planned': {
136
+ CONFLICTS_CLEAR: 'loop.conflicts_evaluated',
137
+ CONFLICTS_FOUND: 'loop.conflicts_evaluated',
138
+ CONFLICTS_BLOCKING: 'loop.blocked',
139
+ CONFLICTS_INVALID: 'loop.failed'
140
+ },
141
+ 'loop.conflicts_evaluated': {
142
+ SIMULATION_READY: 'loop.execution_simulated'
143
+ },
144
+ 'loop.execution_simulated': {
145
+ CONSOLIDATION_READY: 'loop.results_consolidated',
146
+ CONSOLIDATION_SKIPPED: 'loop.results_consolidated',
147
+ CONSOLIDATION_INVALID: 'loop.failed'
148
+ },
149
+ 'loop.results_consolidated': {
150
+ VERIFICATION_READY: 'loop.result_verified',
151
+ VERIFICATION_SKIPPED: 'loop.result_verified',
152
+ VERIFICATION_INVALID: 'loop.failed'
153
+ },
154
+ 'loop.result_verified': {
155
+ PLAN_READY: 'loop.completed'
156
+ }
157
+ };
158
+
159
+ const GATE_DECISIONS = new Set(['EXECUTE', 'BLOCK', 'NEEDS_HUMAN', 'DELEGATE', 'FALLBACK']);
160
+ const ADAPTER_NAMES = new Set([
161
+ 'classifyTask',
162
+ 'selectManager',
163
+ 'selectEngineCandidate',
164
+ 'evaluatePreflight',
165
+ 'evaluateBudget',
166
+ 'evaluatePolicy',
167
+ 'evaluateRuntimeGate',
168
+ 'planDelegation',
169
+ 'planSubtasks',
170
+ 'detectConflicts',
171
+ 'consolidate',
172
+ 'verify'
173
+ ]);
174
+
175
+ const GLOBAL_LIMITS = Object.freeze({
176
+ maxTransitions: 40,
177
+ maxAttempts: 3,
178
+ maxDepth: 2,
179
+ maxSubtasks: 8,
180
+ maxAgentsPerManager: 4
181
+ });
182
+
183
+ const DEFAULT_DECISIONS = Object.freeze({
184
+ classification: null,
185
+ manager: null,
186
+ engineCandidate: null,
187
+ preflight: null,
188
+ budget: null,
189
+ policy: null,
190
+ runtimeGate: null
191
+ });
192
+
193
+ const SENSITIVE_KEYS = ['token', 'secret', 'password', 'authorization', 'cookie', 'apikey', 'env'];
194
+
195
+ export function planOrchestrationStep(input) {
196
+ assertPlainApiObject(input, 'input');
197
+ const {
198
+ state,
199
+ signal = null,
200
+ task,
201
+ context = {},
202
+ limits = {},
203
+ counters = {},
204
+ decisions = {},
205
+ moduleResults = {},
206
+ adapters = {},
207
+ previousTrace = [],
208
+ seenCycleSignatures = []
209
+ } = input;
210
+
211
+ if (typeof state !== 'string') {
212
+ throw new TypeError('state must be a string');
213
+ }
214
+ assertPlainApiObject(context, 'context');
215
+ assertPlainApiObject(limits, 'limits');
216
+ assertPlainApiObject(counters, 'counters');
217
+ assertPlainApiObject(decisions, 'decisions');
218
+ assertPlainApiObject(moduleResults, 'moduleResults');
219
+ assertPlainApiObject(adapters, 'adapters');
220
+ if (!Array.isArray(previousTrace)) {
221
+ throw new TypeError('previousTrace must be an array');
222
+ }
223
+ if (!Array.isArray(seenCycleSignatures)) {
224
+ throw new TypeError('seenCycleSignatures must be an array');
225
+ }
226
+ for (const [name, value] of Object.entries(adapters)) {
227
+ if (ADAPTER_NAMES.has(name) && typeof value !== 'function') {
228
+ throw new TypeError(`adapter ${name} must be a function`);
229
+ }
230
+ }
231
+ if (LOOP_STATES.has(state) && state !== 'loop.initialized' && !TERMINAL_STATES.has(state) && !isValidTask(task)) {
232
+ throw new TypeError('task must be an object with id for non-terminal states');
233
+ }
234
+
235
+ const normalizedLimits = normalizeLimits(limits);
236
+ const normalizedContext = cloneData(context);
237
+ const normalizedCounters = normalizeCounters(counters, normalizedContext);
238
+ const currentDecisions = normalizeDecisions(decisions);
239
+ const currentModules = isPlainObject(moduleResults) ? cloneData(moduleResults) : {};
240
+ const guards = [];
241
+ const warnings = [...normalizedLimits.warnings];
242
+ const errors = [];
243
+ const inconsistencies = [];
244
+ let nextContext = normalizedContext;
245
+ let nextDecisions = currentDecisions;
246
+ let nextModules = currentModules;
247
+ let nextCounters = normalizedCounters;
248
+ let event = signal;
249
+ let nextState = state;
250
+ let reason = '';
251
+ let terminalReason = null;
252
+ let provenance = [];
253
+ let conflicts = extractConflicts(nextModules, nextContext);
254
+
255
+ if (TERMINAL_STATES.has(state)) {
256
+ event = 'TERMINAL_STATE';
257
+ nextState = state;
258
+ reason = 'terminal state does not advance';
259
+ terminalReason = state;
260
+ guards.push('terminal-state');
261
+ return buildStepResult({
262
+ state,
263
+ event,
264
+ nextState,
265
+ reason,
266
+ terminalReason,
267
+ guards,
268
+ decisions: nextDecisions,
269
+ moduleResults: nextModules,
270
+ context: nextContext,
271
+ counters: incrementTransitions(normalizedCounters),
272
+ limits: normalizedLimits.limits,
273
+ previousTrace,
274
+ warnings,
275
+ errors,
276
+ inconsistencies,
277
+ conflicts,
278
+ provenance
279
+ });
280
+ }
281
+
282
+ if (normalizedCounters.transitions >= normalizedLimits.limits.maxTransitions ||
283
+ normalizedCounters.attempts > normalizedLimits.limits.maxAttempts ||
284
+ normalizedCounters.depth > normalizedLimits.limits.maxDepth) {
285
+ event = 'LIMIT_EXCEEDED';
286
+ nextState = 'loop.cancelled_limit';
287
+ reason = 'orchestration loop limit exceeded';
288
+ terminalReason = 'limit_exceeded';
289
+ guards.push('limit');
290
+ errors.push(loopError('LIMIT_EXCEEDED', reason, state, event, 'limits', 'blocking'));
291
+ return buildStepResult({
292
+ state,
293
+ event,
294
+ nextState,
295
+ reason,
296
+ terminalReason,
297
+ guards,
298
+ decisions: nextDecisions,
299
+ moduleResults: nextModules,
300
+ context: nextContext,
301
+ counters: incrementTransitions(normalizedCounters),
302
+ limits: normalizedLimits.limits,
303
+ previousTrace,
304
+ warnings,
305
+ errors,
306
+ inconsistencies,
307
+ conflicts,
308
+ provenance
309
+ });
310
+ }
311
+
312
+ if (!LOOP_STATES.has(state)) {
313
+ event = signal === null ? 'INVALID_TRANSITION' : String(signal);
314
+ nextState = 'loop.cancelled_invalid_transition';
315
+ reason = `unknown loop state: ${state}`;
316
+ terminalReason = 'invalid_transition';
317
+ guards.push('known-state');
318
+ errors.push(loopError('UNKNOWN_STATE', reason, state, event, 'state', 'error'));
319
+ return buildStepResult({
320
+ state,
321
+ event,
322
+ nextState,
323
+ reason,
324
+ terminalReason,
325
+ guards,
326
+ decisions: nextDecisions,
327
+ moduleResults: nextModules,
328
+ context: nextContext,
329
+ counters: incrementTransitions(normalizedCounters),
330
+ limits: normalizedLimits.limits,
331
+ previousTrace,
332
+ warnings,
333
+ errors,
334
+ inconsistencies,
335
+ conflicts,
336
+ provenance
337
+ });
338
+ }
339
+
340
+ if (signal !== null && !EVENTS.has(signal)) {
341
+ event = String(signal);
342
+ nextState = 'loop.cancelled_invalid_transition';
343
+ reason = `unknown event: ${String(signal)}`;
344
+ terminalReason = 'invalid_transition';
345
+ guards.push('known-event');
346
+ errors.push(loopError('UNKNOWN_EVENT', reason, state, event, 'signal', 'error'));
347
+ return buildStepResult({
348
+ state,
349
+ event,
350
+ nextState,
351
+ reason,
352
+ terminalReason,
353
+ guards,
354
+ decisions: nextDecisions,
355
+ moduleResults: nextModules,
356
+ context: nextContext,
357
+ counters: incrementTransitions(normalizedCounters),
358
+ limits: normalizedLimits.limits,
359
+ previousTrace,
360
+ warnings,
361
+ errors,
362
+ inconsistencies,
363
+ conflicts,
364
+ provenance
365
+ });
366
+ }
367
+
368
+ if (signal !== null && !isTransitionAllowed(state, signal)) {
369
+ event = signal;
370
+ nextState = 'loop.cancelled_invalid_transition';
371
+ reason = `invalid transition from ${state} using ${signal}`;
372
+ terminalReason = 'invalid_transition';
373
+ guards.push('valid-transition');
374
+ errors.push(loopError('INVALID_TRANSITION', reason, state, signal, 'signal', 'error'));
375
+ return buildStepResult({
376
+ state,
377
+ event,
378
+ nextState,
379
+ reason,
380
+ terminalReason,
381
+ guards,
382
+ decisions: nextDecisions,
383
+ moduleResults: nextModules,
384
+ context: nextContext,
385
+ counters: incrementTransitions(normalizedCounters),
386
+ limits: normalizedLimits.limits,
387
+ previousTrace,
388
+ warnings,
389
+ errors,
390
+ inconsistencies,
391
+ conflicts,
392
+ provenance
393
+ });
394
+ }
395
+
396
+ const derived = deriveTransition({
397
+ state,
398
+ task,
399
+ context: nextContext,
400
+ limits: normalizedLimits.limits,
401
+ counters: normalizedCounters,
402
+ decisions: nextDecisions,
403
+ moduleResults: nextModules,
404
+ adapters
405
+ });
406
+ if (derived.warnings.length > 0) warnings.push(...derived.warnings);
407
+ if (derived.errors.length > 0) errors.push(...derived.errors);
408
+ if (derived.inconsistencies.length > 0) inconsistencies.push(...derived.inconsistencies);
409
+ nextContext = derived.context;
410
+ nextDecisions = derived.decisions;
411
+ nextModules = derived.moduleResults;
412
+ nextCounters = derived.counters || normalizedCounters;
413
+ provenance = derived.provenance;
414
+ conflicts = derived.conflicts;
415
+
416
+ event = signal === null ? derived.event : signal;
417
+ if (!isTransitionAllowed(state, event)) {
418
+ const received = event;
419
+ event = received;
420
+ nextState = 'loop.cancelled_invalid_transition';
421
+ reason = `invalid transition from ${state} using ${received}`;
422
+ terminalReason = 'invalid_transition';
423
+ guards.push('valid-transition');
424
+ errors.push(loopError('INVALID_TRANSITION', reason, state, received, 'signal', 'error'));
425
+ } else {
426
+ nextState = TRANSITIONS[state][event];
427
+ reason = derived.reason;
428
+ terminalReason = TERMINAL_STATES.has(nextState) ? reasonToTerminal(nextState, derived) : null;
429
+ guards.push(...derived.guards);
430
+ }
431
+
432
+ const candidateSignature = createCycleSignature({
433
+ state,
434
+ event,
435
+ task,
436
+ context: nextContext,
437
+ counters: nextCounters,
438
+ decisions: nextDecisions,
439
+ moduleResults: nextModules,
440
+ conflicts
441
+ });
442
+ if (seenCycleSignatures.includes(candidateSignature) && nextState !== 'loop.cancelled_invalid_transition') {
443
+ event = 'CYCLE_DETECTED';
444
+ nextState = 'loop.cancelled_cycle';
445
+ reason = 'cycle signature was seen previously in this loop';
446
+ terminalReason = 'cycle_detected';
447
+ guards.push('cycle');
448
+ errors.push(loopError('CYCLE_DETECTED', reason, state, event, 'cycleSignature', 'blocking'));
449
+ nextContext = {
450
+ ...nextContext,
451
+ cycleInfo: {
452
+ signature: candidateSignature,
453
+ state,
454
+ event
455
+ }
456
+ };
457
+ }
458
+
459
+ return buildStepResult({
460
+ state,
461
+ event,
462
+ nextState,
463
+ reason,
464
+ terminalReason,
465
+ guards,
466
+ decisions: nextDecisions,
467
+ moduleResults: nextModules,
468
+ context: nextContext,
469
+ counters: incrementTransitions(nextCounters),
470
+ limits: normalizedLimits.limits,
471
+ previousTrace,
472
+ warnings: uniqueMessages(warnings),
473
+ errors,
474
+ inconsistencies,
475
+ conflicts,
476
+ provenance,
477
+ forcedCycleSignature: candidateSignature
478
+ });
479
+ }
480
+
481
+ export function runOrchestrationLoop(input) {
482
+ assertPlainApiObject(input, 'input');
483
+ const {
484
+ task,
485
+ context = {},
486
+ limits = {},
487
+ adapters = {},
488
+ moduleResults = {},
489
+ initialState = 'loop.initialized',
490
+ loopId = null
491
+ } = input;
492
+
493
+ if (typeof initialState !== 'string') {
494
+ throw new TypeError('initialState must be a string');
495
+ }
496
+ if (loopId !== null && (typeof loopId !== 'string' || loopId.trim().length === 0)) {
497
+ throw new TypeError('loopId must be a non-empty string when provided');
498
+ }
499
+ assertPlainApiObject(context, 'context');
500
+ assertPlainApiObject(limits, 'limits');
501
+ assertPlainApiObject(adapters, 'adapters');
502
+ assertPlainApiObject(moduleResults, 'moduleResults');
503
+
504
+ let state = initialState;
505
+ let trace = [];
506
+ let decisions = normalizeDecisions({});
507
+ let modules = cloneData(moduleResults);
508
+ let currentContext = cloneData(context);
509
+ let counters = normalizeCounters({}, currentContext);
510
+ let finalStep = null;
511
+ let seen = [];
512
+ const normalizedLimits = normalizeLimits(limits);
513
+
514
+ while (true) {
515
+ const step = planOrchestrationStep({
516
+ state,
517
+ task,
518
+ context: currentContext,
519
+ limits,
520
+ counters,
521
+ decisions,
522
+ moduleResults: modules,
523
+ adapters,
524
+ previousTrace: trace,
525
+ seenCycleSignatures: seen
526
+ });
527
+ finalStep = step;
528
+ trace = [...trace, cloneData(step.traceEntry)];
529
+ decisions = cloneData(step.decisions);
530
+ modules = cloneData(step.moduleResults);
531
+ currentContext = cloneData(step.context);
532
+ counters = cloneData(step.counters);
533
+ if (step.event !== 'CYCLE_DETECTED') {
534
+ seen = [...seen, step.cycleSignature];
535
+ }
536
+ state = step.nextState;
537
+ if (step.terminal) {
538
+ break;
539
+ }
540
+ if (counters.transitions > normalizedLimits.limits.maxTransitions + 1) {
541
+ break;
542
+ }
543
+ }
544
+
545
+ return buildLoopResult({
546
+ task,
547
+ context: currentContext,
548
+ initialState,
549
+ loopId,
550
+ trace,
551
+ finalStep,
552
+ decisions,
553
+ moduleResults: modules,
554
+ counters,
555
+ limits: normalizedLimits.limits
556
+ });
557
+ }
558
+
559
+ function deriveTransition(input) {
560
+ const base = {
561
+ event: 'INVALID_TRANSITION',
562
+ reason: 'no transition derived',
563
+ guards: [],
564
+ decisions: cloneData(input.decisions),
565
+ moduleResults: cloneData(input.moduleResults),
566
+ context: cloneData(input.context),
567
+ counters: cloneData(input.counters),
568
+ warnings: [],
569
+ errors: [],
570
+ inconsistencies: [],
571
+ conflicts: extractConflicts(input.moduleResults, input.context),
572
+ provenance: []
573
+ };
574
+
575
+ const fail = (event, code, message, field, severity = 'error') => ({
576
+ ...base,
577
+ event,
578
+ reason: message,
579
+ errors: [loopError(code, message, input.state, event, field, severity)]
580
+ });
581
+
582
+ try {
583
+ switch (input.state) {
584
+ case 'loop.initialized':
585
+ if (!isValidTask(input.task)) {
586
+ return fail('INPUT_INVALID', 'INVALID_INPUT', 'task must be an object with id', 'task');
587
+ }
588
+ return {
589
+ ...base,
590
+ event: 'INPUT_VALID',
591
+ reason: 'input task shape is valid',
592
+ guards: ['task-shape'],
593
+ context: {
594
+ ...base.context,
595
+ taskId: String(input.task.id),
596
+ parentTaskId: getParentTaskId(input.task, input.context)
597
+ },
598
+ provenance: [provenance('input', null, 'task', false)]
599
+ };
600
+ case 'loop.input_validated':
601
+ return deriveObjectDecision({
602
+ ...input,
603
+ base,
604
+ eventReady: 'CLASSIFICATION_READY',
605
+ eventInvalid: 'CLASSIFICATION_INVALID',
606
+ decisionKey: 'classification',
607
+ moduleKey: 'classification',
608
+ contextKey: 'classification',
609
+ adapterName: 'classifyTask',
610
+ reasonReady: 'classification is available',
611
+ validate: isPlainObject
612
+ });
613
+ case 'loop.classified':
614
+ return deriveObjectDecision({
615
+ ...input,
616
+ base,
617
+ eventReady: 'MANAGER_READY',
618
+ eventInvalid: 'MANAGER_INVALID',
619
+ decisionKey: 'manager',
620
+ moduleKey: 'manager',
621
+ contextKey: 'manager',
622
+ adapterName: 'selectManager',
623
+ reasonReady: 'manager plan is available',
624
+ validate: value => value === null || isPlainObject(value)
625
+ });
626
+ case 'loop.manager_selected':
627
+ return deriveObjectDecision({
628
+ ...input,
629
+ base,
630
+ eventReady: 'ENGINE_CANDIDATE_READY',
631
+ eventInvalid: 'ENGINE_CANDIDATE_INVALID',
632
+ decisionKey: 'engineCandidate',
633
+ moduleKey: 'engineCandidate',
634
+ contextKey: 'engineCandidate',
635
+ adapterName: 'selectEngineCandidate',
636
+ reasonReady: 'engine candidate is available',
637
+ validate: value => value === null || isPlainObject(value)
638
+ });
639
+ case 'loop.engine_candidate_selected':
640
+ return deriveObjectDecision({
641
+ ...input,
642
+ base,
643
+ eventReady: 'PREFLIGHT_READY',
644
+ eventInvalid: 'PREFLIGHT_INVALID',
645
+ decisionKey: 'preflight',
646
+ moduleKey: 'preflight',
647
+ contextKey: 'preflight',
648
+ adapterName: 'evaluatePreflight',
649
+ reasonReady: 'preflight result is available',
650
+ validate: isPlainObject
651
+ });
652
+ case 'loop.preflight_evaluated':
653
+ return deriveObjectDecision({
654
+ ...input,
655
+ base,
656
+ eventReady: 'BUDGET_READY',
657
+ eventInvalid: 'BUDGET_INVALID',
658
+ decisionKey: 'budget',
659
+ moduleKey: 'budget',
660
+ contextKey: 'budget',
661
+ adapterName: 'evaluateBudget',
662
+ reasonReady: 'budget result is available',
663
+ validate: isPlainObject
664
+ });
665
+ case 'loop.budget_evaluated':
666
+ return deriveObjectDecision({
667
+ ...input,
668
+ base,
669
+ eventReady: 'POLICY_READY',
670
+ eventInvalid: 'POLICY_INVALID',
671
+ decisionKey: 'policy',
672
+ moduleKey: 'policy',
673
+ contextKey: 'policy',
674
+ adapterName: 'evaluatePolicy',
675
+ reasonReady: 'policy result is available',
676
+ validate: isPlainObject
677
+ });
678
+ case 'loop.policy_evaluated':
679
+ return deriveRuntimeGate(input, base);
680
+ case 'loop.runtime_gate_evaluated':
681
+ return deriveGateBranch(input, base);
682
+ case 'loop.execution_planned':
683
+ return deriveExecutionPlan(input, base);
684
+ case 'loop.delegation_planned':
685
+ return deriveSubtasks(input, base);
686
+ case 'loop.subtasks_planned':
687
+ return deriveConflicts(input, base);
688
+ case 'loop.conflicts_evaluated':
689
+ return deriveSimulation(input, base);
690
+ case 'loop.execution_simulated':
691
+ return deriveConsolidation(input, base);
692
+ case 'loop.results_consolidated':
693
+ return deriveVerification(input, base);
694
+ case 'loop.result_verified':
695
+ return {
696
+ ...base,
697
+ event: 'PLAN_READY',
698
+ reason: 'planning pipeline reached terminal completion',
699
+ guards: ['planning-complete']
700
+ };
701
+ default:
702
+ return fail('INVALID_TRANSITION', 'UNKNOWN_STATE', `unknown loop state: ${input.state}`, 'state');
703
+ }
704
+ } catch (error) {
705
+ return fail('INVALID_TRANSITION', 'DERIVE_TRANSITION_FAILED', errorMessage(error), null);
706
+ }
707
+ }
708
+
709
+ function deriveObjectDecision(config) {
710
+ const source = resolveProvidedOrAdapter(config);
711
+ if (source.error) {
712
+ return {
713
+ ...config.base,
714
+ event: config.eventInvalid,
715
+ reason: source.error.message,
716
+ errors: [source.error]
717
+ };
718
+ }
719
+ if (!config.validate(source.value)) {
720
+ return {
721
+ ...config.base,
722
+ event: config.eventInvalid,
723
+ reason: `${config.decisionKey} result is malformed`,
724
+ errors: [loopError('MALFORMED_RESULT', `${config.decisionKey} result is malformed`, config.state, config.eventInvalid, config.decisionKey, 'error')]
725
+ };
726
+ }
727
+ return {
728
+ ...config.base,
729
+ event: config.eventReady,
730
+ reason: config.reasonReady,
731
+ guards: [`${config.decisionKey}-valid`],
732
+ decisions: {
733
+ ...config.base.decisions,
734
+ [config.decisionKey]: cloneData(source.value)
735
+ },
736
+ moduleResults: {
737
+ ...config.base.moduleResults,
738
+ [config.moduleKey]: cloneData(source.value)
739
+ },
740
+ provenance: [provenance(source.provenance, source.adapterName, config.decisionKey, false)]
741
+ };
742
+ }
743
+
744
+ function deriveRuntimeGate(input, base) {
745
+ const source = resolveProvidedOrAdapter({
746
+ ...input,
747
+ base,
748
+ decisionKey: 'runtimeGate',
749
+ moduleKey: 'runtimeGate',
750
+ contextKey: 'runtimeGateResult',
751
+ adapterName: 'evaluateRuntimeGate'
752
+ });
753
+ if (source.error) {
754
+ return {
755
+ ...base,
756
+ event: 'GATE_UNKNOWN',
757
+ reason: source.error.message,
758
+ errors: [source.error]
759
+ };
760
+ }
761
+ if (!isPlainObject(source.value) || !GATE_DECISIONS.has(source.value.decision)) {
762
+ return {
763
+ ...base,
764
+ event: 'GATE_UNKNOWN',
765
+ reason: 'runtime gate decision is unknown or malformed',
766
+ errors: [loopError('UNKNOWN_RUNTIME_GATE_DECISION', 'runtime gate decision is unknown or malformed', input.state, 'GATE_UNKNOWN', 'runtimeGate.decision', 'error')]
767
+ };
768
+ }
769
+ return {
770
+ ...base,
771
+ event: 'RUNTIME_GATE_READY',
772
+ reason: 'runtime gate decision is available',
773
+ guards: ['runtime-gate-valid'],
774
+ decisions: {
775
+ ...base.decisions,
776
+ runtimeGate: cloneData(source.value)
777
+ },
778
+ moduleResults: {
779
+ ...base.moduleResults,
780
+ runtimeGate: cloneData(source.value)
781
+ },
782
+ provenance: [provenance(source.provenance, source.adapterName, 'runtimeGate', false)]
783
+ };
784
+ }
785
+
786
+ function deriveGateBranch(input, base) {
787
+ const gate = input.decisions.runtimeGate || input.moduleResults.runtimeGate || input.context.runtimeGateResult;
788
+ const decision = isPlainObject(gate) ? gate.decision : null;
789
+ if (decision === 'EXECUTE') {
790
+ return {
791
+ ...base,
792
+ event: 'GATE_EXECUTE',
793
+ reason: 'runtime gate permits simulated execution planning',
794
+ guards: ['gate-execute'],
795
+ moduleResults: {
796
+ ...base.moduleResults,
797
+ executionPlan: directExecutionPlan(input.task)
798
+ }
799
+ };
800
+ }
801
+ if (decision === 'DELEGATE') {
802
+ return {
803
+ ...base,
804
+ event: 'GATE_DELEGATE',
805
+ reason: 'runtime gate requires delegation planning only',
806
+ guards: ['gate-delegate'],
807
+ moduleResults: {
808
+ ...base.moduleResults,
809
+ executionPlan: {
810
+ mode: 'delegate',
811
+ executionMode: 'serial',
812
+ order: [String(input.task.id)]
813
+ }
814
+ }
815
+ };
816
+ }
817
+ if (decision === 'BLOCK') {
818
+ return { ...base, event: 'GATE_BLOCK', reason: gateReason(gate, 'runtime gate blocked planning'), guards: ['gate-block'] };
819
+ }
820
+ if (decision === 'NEEDS_HUMAN') {
821
+ return { ...base, event: 'GATE_NEEDS_HUMAN', reason: gateReason(gate, 'runtime gate needs human decision'), guards: ['gate-needs-human'] };
822
+ }
823
+ if (decision === 'FALLBACK') {
824
+ return {
825
+ ...base,
826
+ event: 'GATE_FALLBACK',
827
+ reason: gateReason(gate, 'runtime gate requires fallback integration'),
828
+ guards: ['gate-fallback'],
829
+ moduleResults: {
830
+ ...base.moduleResults,
831
+ fallbackRequired: {
832
+ required: true,
833
+ applied: false,
834
+ decision: 'FALLBACK'
835
+ }
836
+ }
837
+ };
838
+ }
839
+ return {
840
+ ...base,
841
+ event: 'GATE_UNKNOWN',
842
+ reason: 'runtime gate decision is unknown',
843
+ errors: [loopError('UNKNOWN_RUNTIME_GATE_DECISION', 'runtime gate decision is unknown', input.state, 'GATE_UNKNOWN', 'runtimeGate.decision', 'error')]
844
+ };
845
+ }
846
+
847
+ function deriveExecutionPlan(input, base) {
848
+ const gate = input.decisions.runtimeGate || {};
849
+ const manager = input.decisions.manager || {};
850
+ if (gate.decision === 'DELEGATE' || manager.decision === 'decompose') {
851
+ const source = resolveProvidedOrAdapter({
852
+ ...input,
853
+ base,
854
+ decisionKey: 'plannedDelegation',
855
+ moduleKey: 'delegation',
856
+ contextKey: 'delegation',
857
+ adapterName: 'planDelegation'
858
+ });
859
+ if (source.error || !isPlainObject(source.value)) {
860
+ return {
861
+ ...base,
862
+ event: 'DELEGATION_INVALID',
863
+ reason: source.error ? source.error.message : 'delegation result is malformed',
864
+ errors: [source.error || loopError('MALFORMED_DELEGATION', 'delegation result is malformed', input.state, 'DELEGATION_INVALID', 'delegation', 'error')]
865
+ };
866
+ }
867
+ const validation = validateDelegation(source.value, input.limits);
868
+ if (validation.errors.length > 0) {
869
+ return {
870
+ ...base,
871
+ event: validation.blocking ? 'DELEGATION_INVALID' : 'DELEGATION_INVALID',
872
+ reason: validation.errors[0].message,
873
+ errors: validation.errors,
874
+ conflicts: validation.conflicts
875
+ };
876
+ }
877
+ return {
878
+ ...base,
879
+ event: 'DELEGATION_READY',
880
+ reason: 'delegation plan is available',
881
+ guards: ['delegation-valid', 'serial-only'],
882
+ moduleResults: {
883
+ ...base.moduleResults,
884
+ delegation: cloneData(source.value),
885
+ plannedSubtasks: cloneData(source.value.subtasks || []),
886
+ assignments: cloneData(source.value.assignments || [])
887
+ },
888
+ counters: {
889
+ ...base.counters,
890
+ subtasks: Array.isArray(source.value.subtasks) ? source.value.subtasks.length : 0,
891
+ agents: Array.isArray(source.value.assignments) ? source.value.assignments.length : 0
892
+ },
893
+ provenance: [provenance(source.provenance, source.adapterName, 'delegation', true)]
894
+ };
895
+ }
896
+ return {
897
+ ...base,
898
+ event: 'PLAN_READY',
899
+ reason: 'direct serial execution plan is ready',
900
+ guards: ['serial-plan'],
901
+ moduleResults: {
902
+ ...base.moduleResults,
903
+ executionPlan: directExecutionPlan(input.task)
904
+ }
905
+ };
906
+ }
907
+
908
+ function deriveSubtasks(input, base) {
909
+ const source = resolveProvidedOrAdapter({
910
+ ...input,
911
+ base,
912
+ decisionKey: 'plannedSubtasks',
913
+ moduleKey: 'plannedSubtasks',
914
+ contextKey: 'plannedSubtasks',
915
+ adapterName: 'planSubtasks',
916
+ fallbackValue: input.moduleResults.plannedSubtasks
917
+ });
918
+ const subtasks = Array.isArray(source.value)
919
+ ? source.value
920
+ : isPlainObject(source.value) && Array.isArray(source.value.subtasks)
921
+ ? source.value.subtasks
922
+ : source.value;
923
+ if (source.error || !Array.isArray(subtasks)) {
924
+ return {
925
+ ...base,
926
+ event: 'SUBTASKS_INVALID',
927
+ reason: source.error ? source.error.message : 'subtasks result is malformed',
928
+ errors: [source.error || loopError('MALFORMED_SUBTASKS', 'subtasks result is malformed', input.state, 'SUBTASKS_INVALID', 'subtasks', 'error')]
929
+ };
930
+ }
931
+ if (input.moduleResults.assignments !== undefined && !Array.isArray(input.moduleResults.assignments)) {
932
+ return {
933
+ ...base,
934
+ event: 'SUBTASKS_INVALID',
935
+ reason: 'assignments must be an array',
936
+ errors: [loopError('MALFORMED_ASSIGNMENTS', 'assignments must be an array', input.state, 'SUBTASKS_INVALID', 'assignments', 'error')]
937
+ };
938
+ }
939
+ const validation = validateSubtasks(subtasks, input.moduleResults.assignments || [], input.limits, input.context);
940
+ if (validation.errors.length > 0) {
941
+ return {
942
+ ...base,
943
+ event: validation.blocking ? 'SUBTASKS_INVALID' : 'SUBTASKS_INVALID',
944
+ reason: validation.errors[0].message,
945
+ errors: validation.errors,
946
+ conflicts: validation.conflicts
947
+ };
948
+ }
949
+ return {
950
+ ...base,
951
+ event: 'SUBTASKS_READY',
952
+ reason: 'subtask plan is valid and serial',
953
+ guards: ['subtasks-valid', 'subtask-limits'],
954
+ moduleResults: {
955
+ ...base.moduleResults,
956
+ plannedSubtasks: cloneData(subtasks),
957
+ serialOrder: subtasks.map(item => String(item.id))
958
+ },
959
+ counters: {
960
+ ...base.counters,
961
+ subtasks: subtasks.length
962
+ },
963
+ provenance: [provenance(source.provenance, source.adapterName, 'subtasks', true)]
964
+ };
965
+ }
966
+
967
+ function deriveConflicts(input, base) {
968
+ const source = resolveProvidedOrAdapter({
969
+ ...input,
970
+ base,
971
+ decisionKey: 'conflicts',
972
+ moduleKey: 'conflicts',
973
+ contextKey: 'conflicts',
974
+ adapterName: 'detectConflicts',
975
+ fallbackValue: []
976
+ });
977
+ if (source.error || !Array.isArray(source.value)) {
978
+ return {
979
+ ...base,
980
+ event: 'CONFLICTS_INVALID',
981
+ reason: source.error ? source.error.message : 'conflict result is malformed',
982
+ errors: [source.error || loopError('MALFORMED_CONFLICT', 'conflict result is malformed', input.state, 'CONFLICTS_INVALID', 'conflicts', 'error')]
983
+ };
984
+ }
985
+ const normalized = normalizeConflicts(source.value, input.moduleResults.plannedSubtasks || []);
986
+ if (normalized.errors.length > 0) {
987
+ return {
988
+ ...base,
989
+ event: 'CONFLICTS_INVALID',
990
+ reason: normalized.errors[0].message,
991
+ errors: normalized.errors,
992
+ conflicts: normalized.conflicts
993
+ };
994
+ }
995
+ if (normalized.blocking) {
996
+ return {
997
+ ...base,
998
+ event: 'CONFLICTS_BLOCKING',
999
+ reason: 'blocking file conflict prevents safe planning',
1000
+ guards: ['conflicts-blocking'],
1001
+ errors: [loopError('BLOCKING_CONFLICT', 'blocking file conflict prevents safe planning', input.state, 'CONFLICTS_BLOCKING', 'conflicts', 'blocking')],
1002
+ conflicts: normalized.conflicts,
1003
+ moduleResults: {
1004
+ ...base.moduleResults,
1005
+ conflicts: cloneData(normalized.conflicts)
1006
+ }
1007
+ };
1008
+ }
1009
+ return {
1010
+ ...base,
1011
+ event: normalized.conflicts.length > 0 ? 'CONFLICTS_FOUND' : 'CONFLICTS_CLEAR',
1012
+ reason: normalized.conflicts.length > 0 ? 'serializable conflicts were recorded' : 'no file conflicts were found',
1013
+ guards: ['conflicts-evaluated'],
1014
+ conflicts: normalized.conflicts,
1015
+ moduleResults: {
1016
+ ...base.moduleResults,
1017
+ conflicts: cloneData(normalized.conflicts)
1018
+ },
1019
+ provenance: [provenance(source.provenance, source.adapterName, 'conflicts', true)]
1020
+ };
1021
+ }
1022
+
1023
+ function deriveSimulation(input, base) {
1024
+ const order = serialOrderFor(input.task, input.moduleResults);
1025
+ const simulation = {
1026
+ simulated: true,
1027
+ executed: false,
1028
+ executionMode: 'serial',
1029
+ order,
1030
+ engineCandidate: cloneData(input.decisions.engineCandidate),
1031
+ workerCalled: false,
1032
+ engineCalled: false,
1033
+ commandsCalled: false,
1034
+ hostValidationCalled: false,
1035
+ fallbackApplied: false,
1036
+ evidenceCreated: false,
1037
+ workspaceDiffCreated: false
1038
+ };
1039
+ return {
1040
+ ...base,
1041
+ event: 'SIMULATION_READY',
1042
+ reason: 'dry-run execution summary was simulated without effects',
1043
+ guards: ['dry-run', 'no-effects'],
1044
+ moduleResults: {
1045
+ ...base.moduleResults,
1046
+ simulation
1047
+ },
1048
+ provenance: [provenance('loop', null, 'simulation', true)]
1049
+ };
1050
+ }
1051
+
1052
+ function deriveConsolidation(input, base) {
1053
+ const supplied = firstDefined(input.context.consolidatedResult, input.moduleResults.consolidation);
1054
+ if (supplied !== undefined) {
1055
+ if (!isValidConsolidation(supplied)) {
1056
+ return {
1057
+ ...base,
1058
+ event: 'CONSOLIDATION_INVALID',
1059
+ reason: 'consolidated result is malformed',
1060
+ errors: [loopError('MALFORMED_CONSOLIDATION', 'consolidated result is malformed', input.state, 'CONSOLIDATION_INVALID', 'consolidation', 'error')]
1061
+ };
1062
+ }
1063
+ return {
1064
+ ...base,
1065
+ event: 'CONSOLIDATION_READY',
1066
+ reason: 'supplied consolidated result was preserved',
1067
+ guards: ['consolidation-supplied'],
1068
+ moduleResults: {
1069
+ ...base.moduleResults,
1070
+ consolidation: cloneData(supplied)
1071
+ },
1072
+ provenance: [provenance(Object.prototype.hasOwnProperty.call(input.context, 'consolidatedResult') ? 'input' : 'module-result', null, 'consolidation', false)]
1073
+ };
1074
+ }
1075
+ const subtaskResults = firstDefined(input.context.simulatedSubtaskResults, input.moduleResults.simulatedSubtaskResults);
1076
+ if (subtaskResults !== undefined) {
1077
+ const source = callAdapter('consolidate', input.adapters.consolidate, {
1078
+ task: cloneData(input.task),
1079
+ subtasks: cloneData(input.moduleResults.plannedSubtasks || []),
1080
+ subtaskResults: cloneData(subtaskResults),
1081
+ simulated: true,
1082
+ context: cloneData(input.context)
1083
+ }, input.state, 'CONSOLIDATION_INVALID');
1084
+ if (source.error || !isValidConsolidation(source.value)) {
1085
+ return {
1086
+ ...base,
1087
+ event: 'CONSOLIDATION_INVALID',
1088
+ reason: source.error ? source.error.message : 'consolidated result is malformed',
1089
+ errors: [source.error || loopError('MALFORMED_CONSOLIDATION', 'consolidated result is malformed', input.state, 'CONSOLIDATION_INVALID', 'consolidation', 'error')]
1090
+ };
1091
+ }
1092
+ return {
1093
+ ...base,
1094
+ event: 'CONSOLIDATION_READY',
1095
+ reason: 'simulated subtask results were consolidated by adapter',
1096
+ guards: ['simulated-results-supplied'],
1097
+ moduleResults: {
1098
+ ...base.moduleResults,
1099
+ consolidation: cloneData(source.value)
1100
+ },
1101
+ provenance: [provenance('adapter', 'consolidate', 'consolidation', true)]
1102
+ };
1103
+ }
1104
+ return {
1105
+ ...base,
1106
+ event: 'CONSOLIDATION_SKIPPED',
1107
+ reason: 'no subtask results were supplied',
1108
+ guards: ['no-consolidation-input'],
1109
+ moduleResults: {
1110
+ ...base.moduleResults,
1111
+ consolidation: {
1112
+ mode: 'not_run',
1113
+ reason: 'no subtask results were supplied'
1114
+ }
1115
+ },
1116
+ provenance: [provenance('loop', null, 'consolidation', true)]
1117
+ };
1118
+ }
1119
+
1120
+ function deriveVerification(input, base) {
1121
+ const supplied = firstDefined(input.context.verificationResult, input.moduleResults.verification);
1122
+ if (supplied !== undefined) {
1123
+ if (!isValidVerification(supplied)) {
1124
+ return {
1125
+ ...base,
1126
+ event: 'VERIFICATION_INVALID',
1127
+ reason: 'verification result is malformed',
1128
+ errors: [loopError('MALFORMED_VERIFICATION', 'verification result is malformed', input.state, 'VERIFICATION_INVALID', 'verification', 'error')]
1129
+ };
1130
+ }
1131
+ return {
1132
+ ...base,
1133
+ event: 'VERIFICATION_READY',
1134
+ reason: 'supplied verification result was preserved',
1135
+ guards: ['verification-supplied'],
1136
+ moduleResults: {
1137
+ ...base.moduleResults,
1138
+ verification: cloneData(supplied)
1139
+ },
1140
+ provenance: [provenance(Object.prototype.hasOwnProperty.call(input.context, 'verificationResult') ? 'input' : 'module-result', null, 'verification', false)]
1141
+ };
1142
+ }
1143
+ const consolidation = input.moduleResults.consolidation;
1144
+ if (isValidConsolidation(consolidation) && typeof input.adapters.verify === 'function') {
1145
+ const source = callAdapter('verify', input.adapters.verify, {
1146
+ task: cloneData(input.task),
1147
+ consolidatedResult: cloneData(consolidation),
1148
+ acceptanceCriteria: cloneData(input.context.acceptanceCriteria || [])
1149
+ }, input.state, 'VERIFICATION_INVALID');
1150
+ if (source.error || !isValidVerification(source.value)) {
1151
+ return {
1152
+ ...base,
1153
+ event: 'VERIFICATION_INVALID',
1154
+ reason: source.error ? source.error.message : 'verification result is malformed',
1155
+ errors: [source.error || loopError('MALFORMED_VERIFICATION', 'verification result is malformed', input.state, 'VERIFICATION_INVALID', 'verification', 'error')]
1156
+ };
1157
+ }
1158
+ return {
1159
+ ...base,
1160
+ event: 'VERIFICATION_READY',
1161
+ reason: 'consolidated result was verified by adapter',
1162
+ guards: ['consolidated-result-supplied'],
1163
+ moduleResults: {
1164
+ ...base.moduleResults,
1165
+ verification: cloneData(source.value)
1166
+ },
1167
+ provenance: [provenance('adapter', 'verify', 'verification', true)]
1168
+ };
1169
+ }
1170
+ return {
1171
+ ...base,
1172
+ event: 'VERIFICATION_SKIPPED',
1173
+ reason: 'no consolidated result was supplied',
1174
+ guards: ['no-verification-input'],
1175
+ moduleResults: {
1176
+ ...base.moduleResults,
1177
+ verification: {
1178
+ mode: 'not_run',
1179
+ reason: 'no consolidated result was supplied'
1180
+ }
1181
+ },
1182
+ provenance: [provenance('loop', null, 'verification', true)]
1183
+ };
1184
+ }
1185
+
1186
+ function resolveProvidedOrAdapter(config) {
1187
+ const supplied = firstDefined(
1188
+ config.context[config.contextKey],
1189
+ config.moduleResults[config.moduleKey]
1190
+ );
1191
+ if (supplied !== undefined) {
1192
+ return {
1193
+ value: cloneData(supplied),
1194
+ provenance: Object.prototype.hasOwnProperty.call(config.context, config.contextKey) ? 'input' : 'module-result',
1195
+ adapterName: null
1196
+ };
1197
+ }
1198
+ const adapter = config.adapters[config.adapterName];
1199
+ if (typeof adapter !== 'function' && config.fallbackValue !== undefined) {
1200
+ return {
1201
+ value: cloneData(config.fallbackValue),
1202
+ provenance: 'loop',
1203
+ adapterName: null
1204
+ };
1205
+ }
1206
+ const called = callAdapter(config.adapterName, adapter, {
1207
+ task: cloneData(config.task),
1208
+ context: cloneData(config.context),
1209
+ decisions: cloneData(config.decisions),
1210
+ moduleResults: cloneData(config.moduleResults),
1211
+ limits: cloneData(config.limits),
1212
+ counters: cloneData(config.counters)
1213
+ }, config.state, config.eventInvalid || 'INVALID_TRANSITION');
1214
+ if (called.error) {
1215
+ return called;
1216
+ }
1217
+ return {
1218
+ value: called.value,
1219
+ provenance: 'adapter',
1220
+ adapterName: config.adapterName
1221
+ };
1222
+ }
1223
+
1224
+ function callAdapter(name, adapter, payload, state, event) {
1225
+ if (typeof adapter !== 'function') {
1226
+ return {
1227
+ error: loopError('ADAPTER_MISSING', `adapter ${name} is required for this state`, state, event, name, 'error')
1228
+ };
1229
+ }
1230
+ let value;
1231
+ try {
1232
+ value = adapter(payload);
1233
+ } catch (error) {
1234
+ return {
1235
+ error: loopError('ADAPTER_THROWN', `adapter ${name} failed: ${errorMessage(error)}`, state, event, name, 'error')
1236
+ };
1237
+ }
1238
+ if (value === undefined) {
1239
+ return {
1240
+ error: loopError('ADAPTER_RETURNED_UNDEFINED', `adapter ${name} returned undefined`, state, event, name, 'error')
1241
+ };
1242
+ }
1243
+ if (isThenable(value)) {
1244
+ return {
1245
+ error: loopError('ADAPTER_RETURNED_THENABLE', `adapter ${name} returned a Promise or thenable`, state, event, name, 'error')
1246
+ };
1247
+ }
1248
+ return { value: cloneData(value) };
1249
+ }
1250
+
1251
+ function validateDelegation(plan, limits) {
1252
+ const errors = [];
1253
+ const conflicts = [];
1254
+ if (plan.executionMode !== 'serial') {
1255
+ errors.push(loopError('NON_SERIAL_DELEGATION', 'delegation executionMode must be serial', 'loop.execution_planned', 'DELEGATION_INVALID', 'delegation.executionMode', 'blocking'));
1256
+ }
1257
+ if (!Array.isArray(plan.subtasks)) {
1258
+ errors.push(loopError('MALFORMED_DELEGATION', 'delegation subtasks must be an array', 'loop.execution_planned', 'DELEGATION_INVALID', 'delegation.subtasks', 'error'));
1259
+ }
1260
+ if (!Array.isArray(plan.assignments)) {
1261
+ errors.push(loopError('MALFORMED_DELEGATION', 'delegation assignments must be an array', 'loop.execution_planned', 'DELEGATION_INVALID', 'delegation.assignments', 'error'));
1262
+ }
1263
+ if (Array.isArray(plan.assignments)) {
1264
+ const count = plan.assignments.length;
1265
+ if (count > limits.maxAgentsPerManager) {
1266
+ errors.push(loopError('AGENT_LIMIT_EXCEEDED', `assignments exceed maxAgentsPerManager ${limits.maxAgentsPerManager}`, 'loop.execution_planned', 'DELEGATION_INVALID', 'assignments', 'blocking'));
1267
+ }
1268
+ }
1269
+ if (Array.isArray(plan.subtasks) && plan.subtasks.length > limits.maxSubtasks) {
1270
+ errors.push(loopError('SUBTASK_LIMIT_EXCEEDED', `subtasks exceed maxSubtasks ${limits.maxSubtasks}`, 'loop.execution_planned', 'DELEGATION_INVALID', 'subtasks', 'blocking'));
1271
+ }
1272
+ return { errors, conflicts, blocking: errors.some(error => error.severity === 'blocking') };
1273
+ }
1274
+
1275
+ function validateSubtasks(subtasks, assignments, limits, context) {
1276
+ const errors = [];
1277
+ const conflicts = [];
1278
+ if (Number(context.orchestrationDepth || 0) > limits.maxDepth) {
1279
+ errors.push(loopError('DEPTH_LIMIT_EXCEEDED', `orchestrationDepth exceeds maxDepth ${limits.maxDepth}`, 'loop.delegation_planned', 'SUBTASKS_INVALID', 'context.orchestrationDepth', 'blocking'));
1280
+ }
1281
+ if (subtasks.length > limits.maxSubtasks) {
1282
+ errors.push(loopError('SUBTASK_LIMIT_EXCEEDED', `subtasks exceed maxSubtasks ${limits.maxSubtasks}`, 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks', 'blocking'));
1283
+ }
1284
+ const ids = subtasks.map(item => isPlainObject(item) ? item.id : null);
1285
+ const idSet = new Set(ids.filter(value => typeof value === 'string' && value.length > 0));
1286
+ if (idSet.size !== ids.length) {
1287
+ errors.push(loopError('INVALID_SUBTASK_REFERENCE', 'subtask ids must be unique non-empty strings', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.id', 'error'));
1288
+ }
1289
+ for (const subtask of subtasks) {
1290
+ if (!isPlainObject(subtask)) {
1291
+ errors.push(loopError('MALFORMED_SUBTASK', 'subtask must be an object', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks', 'error'));
1292
+ continue;
1293
+ }
1294
+ if (subtask.parentTaskId !== undefined && subtask.parentTaskId !== context.taskId) {
1295
+ errors.push(loopError('INVALID_SUBTASK_REFERENCE', 'subtask parentTaskId does not match parent context', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.parentTaskId', 'error'));
1296
+ }
1297
+ if (subtask.expectedFiles !== undefined && !Array.isArray(subtask.expectedFiles)) {
1298
+ errors.push(loopError('MALFORMED_SUBTASK', 'expectedFiles must be an array when present', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.expectedFiles', 'error'));
1299
+ }
1300
+ if (Array.isArray(subtask.expectedFiles)) {
1301
+ for (const file of subtask.expectedFiles) {
1302
+ if (typeof file !== 'string' || file.length === 0) {
1303
+ errors.push(loopError('MALFORMED_SUBTASK', 'expectedFiles entries must be non-empty strings', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.expectedFiles', 'error'));
1304
+ }
1305
+ }
1306
+ }
1307
+ if (Array.isArray(subtask.blockedBy)) {
1308
+ for (const dependency of subtask.blockedBy) {
1309
+ if (!idSet.has(dependency)) {
1310
+ errors.push(loopError('INVALID_SUBTASK_REFERENCE', `blockedBy references missing subtask ${String(dependency)}`, 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.blockedBy', 'error'));
1311
+ }
1312
+ }
1313
+ } else if (subtask.blockedBy !== undefined) {
1314
+ errors.push(loopError('MALFORMED_SUBTASK', 'blockedBy must be an array when present', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.blockedBy', 'error'));
1315
+ }
1316
+ }
1317
+ if (hasDependencyCycle(subtasks)) {
1318
+ errors.push(loopError('SUBTASK_DEPENDENCY_CYCLE', 'blockedBy references contain a cycle', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'subtasks.blockedBy', 'blocking'));
1319
+ }
1320
+ for (const assignment of assignments) {
1321
+ if (!isPlainObject(assignment) || typeof assignment.subtaskId !== 'string' || !idSet.has(assignment.subtaskId)) {
1322
+ errors.push(loopError('INVALID_ASSIGNMENT_REFERENCE', 'assignment references a missing subtask', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'assignments.subtaskId', 'error'));
1323
+ continue;
1324
+ }
1325
+ if (!(typeof assignment.candidateEngine === 'string' || assignment.candidateEngine === null)) {
1326
+ errors.push(loopError('INVALID_ASSIGNMENT_REFERENCE', 'assignment candidateEngine must be a string or null', 'loop.delegation_planned', 'SUBTASKS_INVALID', 'assignments.candidateEngine', 'error'));
1327
+ }
1328
+ }
1329
+ return { errors, conflicts, blocking: errors.some(error => error.severity === 'blocking') };
1330
+ }
1331
+
1332
+ function normalizeConflicts(conflicts, subtasks) {
1333
+ const errors = [];
1334
+ const normalized = [];
1335
+ for (const conflict of conflicts) {
1336
+ if (!isPlainObject(conflict) ||
1337
+ typeof conflict.type !== 'string' ||
1338
+ !['low', 'medium', 'high', 'critical'].includes(conflict.severity) ||
1339
+ typeof conflict.reason !== 'string') {
1340
+ errors.push(loopError('MALFORMED_CONFLICT', 'conflict entry is malformed', 'loop.subtasks_planned', 'CONFLICTS_INVALID', 'conflicts', 'error'));
1341
+ continue;
1342
+ }
1343
+ normalized.push(cloneData(conflict));
1344
+ }
1345
+ for (const conflict of inferConflictRisks(subtasks)) {
1346
+ normalized.push(conflict);
1347
+ }
1348
+ const blocking = normalized.some(conflict => {
1349
+ const target = String(conflict.file || conflict.path || '').replace(/\\/g, '/').toLowerCase();
1350
+ return conflict.severity === 'critical' ||
1351
+ conflict.type === 'parallel-write' ||
1352
+ isSensitivePath(target) ||
1353
+ /^[a-z]:[\\/]/i.test(target);
1354
+ });
1355
+ return {
1356
+ errors,
1357
+ conflicts: sortConflicts(normalized),
1358
+ blocking
1359
+ };
1360
+ }
1361
+
1362
+ function inferConflictRisks(subtasks) {
1363
+ const conflicts = [];
1364
+ const fileOwners = new Map();
1365
+ for (const subtask of Array.isArray(subtasks) ? subtasks : []) {
1366
+ if (!isPlainObject(subtask)) continue;
1367
+ if (Array.isArray(subtask.expectedFiles) && subtask.expectedFiles.length === 0 && hasWriteFiles(subtask)) {
1368
+ conflicts.push({
1369
+ type: 'missing-expected-files',
1370
+ severity: 'medium',
1371
+ reason: 'write subtask does not declare expectedFiles',
1372
+ subtaskId: subtask.id
1373
+ });
1374
+ }
1375
+ const expectedFiles = Array.isArray(subtask.expectedFiles) ? subtask.expectedFiles : [];
1376
+ for (const file of expectedFiles) {
1377
+ const key = String(file);
1378
+ const owners = fileOwners.get(key) || [];
1379
+ owners.push(String(subtask.id));
1380
+ fileOwners.set(key, owners);
1381
+ if (isSensitivePath(key)) {
1382
+ conflicts.push({
1383
+ type: 'sensitive-path',
1384
+ severity: 'critical',
1385
+ reason: 'sensitive or outside path is not safe for planned delegation',
1386
+ subtaskId: subtask.id,
1387
+ file: key
1388
+ });
1389
+ }
1390
+ }
1391
+ if (subtask.parallelizable === true && hasWriteFiles(subtask)) {
1392
+ conflicts.push({
1393
+ type: 'parallel-write',
1394
+ severity: 'critical',
1395
+ reason: 'parallelizable file writes are not allowed',
1396
+ subtaskId: subtask.id
1397
+ });
1398
+ }
1399
+ }
1400
+ for (const [file, owners] of fileOwners.entries()) {
1401
+ if (owners.length > 1) {
1402
+ conflicts.push({
1403
+ type: 'file-write-conflict',
1404
+ severity: 'high',
1405
+ reason: 'multiple subtasks target the same file',
1406
+ subtaskIds: owners,
1407
+ file
1408
+ });
1409
+ }
1410
+ }
1411
+ const files = [...fileOwners.keys()].sort(compareStrings);
1412
+ for (let index = 0; index < files.length; index += 1) {
1413
+ for (let other = index + 1; other < files.length; other += 1) {
1414
+ const left = normalizePathForCompare(files[index]);
1415
+ const right = normalizePathForCompare(files[other]);
1416
+ if (left && right && (right.startsWith(`${left}/`) || left.startsWith(`${right}/`))) {
1417
+ conflicts.push({
1418
+ type: 'path-overlap',
1419
+ severity: 'critical',
1420
+ reason: 'parent/child file paths require explicit precedence before delegation',
1421
+ subtaskIds: uniqueSortedStrings([...(fileOwners.get(files[index]) || []), ...(fileOwners.get(files[other]) || [])]),
1422
+ file: files[index],
1423
+ path: files[other]
1424
+ });
1425
+ }
1426
+ }
1427
+ }
1428
+ return conflicts;
1429
+ }
1430
+
1431
+ function buildStepResult(input) {
1432
+ const terminal = TERMINAL_STATES.has(input.nextState);
1433
+ const counters = cloneData(input.counters);
1434
+ const cycleSignature = input.forcedCycleSignature || createCycleSignature({
1435
+ state: input.state,
1436
+ event: input.event,
1437
+ task: null,
1438
+ context: input.context,
1439
+ counters,
1440
+ decisions: input.decisions,
1441
+ moduleResults: input.moduleResults,
1442
+ conflicts: input.conflicts
1443
+ });
1444
+ const traceEntry = {
1445
+ schemaVersion: SCHEMA_VERSION,
1446
+ sequence: input.previousTrace.length,
1447
+ previousState: input.state,
1448
+ event: input.event,
1449
+ nextState: input.nextState,
1450
+ terminal,
1451
+ terminalReason: input.terminalReason,
1452
+ reason: input.reason,
1453
+ guards: cloneData(input.guards),
1454
+ decisions: redactSensitive(input.decisions),
1455
+ taskId: stringOrNull(input.context.taskId),
1456
+ parentTaskId: stringOrNull(input.context.parentTaskId),
1457
+ manager: managerIdFrom(input.decisions.manager),
1458
+ engineCandidate: engineIdFrom(input.decisions.engineCandidate),
1459
+ runtimeGateDecision: runtimeGateDecision(input.decisions.runtimeGate),
1460
+ limits: cloneData(input.limits),
1461
+ counters,
1462
+ warnings: uniqueMessages(input.warnings),
1463
+ errors: sortErrors(input.errors),
1464
+ conflicts: redactSensitive(sortConflicts(input.conflicts)),
1465
+ provenance: cloneData(input.provenance),
1466
+ cycleSignature
1467
+ };
1468
+ return {
1469
+ schemaVersion: SCHEMA_VERSION,
1470
+ previousState: input.state,
1471
+ event: input.event,
1472
+ nextState: input.nextState,
1473
+ terminal,
1474
+ terminalReason: input.terminalReason,
1475
+ reason: input.reason,
1476
+ guards: cloneData(input.guards),
1477
+ decisions: cloneData(input.decisions),
1478
+ moduleResults: cloneData(input.moduleResults),
1479
+ context: cloneData(input.context),
1480
+ counters,
1481
+ cycleSignature,
1482
+ traceEntry,
1483
+ warnings: uniqueMessages(input.warnings),
1484
+ errors: sortErrors(input.errors),
1485
+ inconsistencies: sortErrors(input.inconsistencies)
1486
+ };
1487
+ }
1488
+
1489
+ function buildLoopResult(input) {
1490
+ const finalState = input.finalStep ? input.finalStep.nextState : input.initialState;
1491
+ const warnings = uniqueMessages(input.trace.flatMap(entry => entry.warnings));
1492
+ const errors = sortErrors(input.trace.flatMap(entry => entry.errors));
1493
+ const inconsistencies = sortErrors(input.finalStep ? input.finalStep.inconsistencies : []);
1494
+ const consolidation = cloneData(input.moduleResults.consolidation || null);
1495
+ const verification = cloneData(input.moduleResults.verification || null);
1496
+ const verificationOutcome = isPlainObject(verification) && verification.mode !== 'not_run'
1497
+ ? stringOrNull(verification.verificationOutcome || verification.outcome)
1498
+ : null;
1499
+ const conflicts = sortConflicts(input.moduleResults.conflicts || []);
1500
+ const result = {
1501
+ schemaVersion: SCHEMA_VERSION,
1502
+ loopId: input.loopId || defaultLoopId(input.task, input.context),
1503
+ dryRun: true,
1504
+ executed: false,
1505
+ taskId: isValidTask(input.task) ? String(input.task.id) : null,
1506
+ parentTaskId: getParentTaskId(input.task, input.context),
1507
+ initialState: input.initialState,
1508
+ finalLoopState: finalState,
1509
+ terminalReason: input.finalStep ? input.finalStep.terminalReason : null,
1510
+ completedPlanning: finalState === 'loop.completed',
1511
+ trace: cloneData(input.trace),
1512
+ decisions: normalizeDecisions(input.decisions),
1513
+ plannedSubtasks: cloneData(input.moduleResults.plannedSubtasks || []),
1514
+ plannedDelegation: cloneData(input.moduleResults.delegation || null),
1515
+ conflicts,
1516
+ selectedManager: managerIdFrom(input.decisions.manager),
1517
+ engineCandidate: engineIdFrom(input.decisions.engineCandidate),
1518
+ runtimeGateDecision: runtimeGateDecision(input.decisions.runtimeGate),
1519
+ simulation: cloneData(input.moduleResults.simulation || null),
1520
+ consolidation,
1521
+ verification,
1522
+ verificationOutcome,
1523
+ warnings,
1524
+ errors,
1525
+ inconsistencies,
1526
+ limits: cloneData(input.limits),
1527
+ limitsConsumed: cloneData(input.counters),
1528
+ cycleInfo: cloneData(input.context.cycleInfo || null),
1529
+ recommendedNextAction: recommendedNextAction(finalState, input.decisions.runtimeGate, consolidation, verification, conflicts),
1530
+ summary: ''
1531
+ };
1532
+ result.summary = `Loop ${stateSummary(finalState)}: planning=${String(result.completedPlanning)}, executed=false, gate=${result.runtimeGateDecision || 'none'}, verification=${verificationOutcome || 'not_run'}.`;
1533
+ return cloneData(result);
1534
+ }
1535
+
1536
+ function isTransitionAllowed(state, event) {
1537
+ return Object.prototype.hasOwnProperty.call(TRANSITIONS, state) &&
1538
+ Object.prototype.hasOwnProperty.call(TRANSITIONS[state], event);
1539
+ }
1540
+
1541
+ function normalizeLimits(limits) {
1542
+ const resolved = {};
1543
+ const warnings = [];
1544
+ for (const [key, ceiling] of Object.entries(GLOBAL_LIMITS)) {
1545
+ const value = limits[key];
1546
+ if (value === undefined) {
1547
+ resolved[key] = ceiling;
1548
+ } else if (Number.isInteger(value) && value > 0) {
1549
+ if (value > ceiling) {
1550
+ resolved[key] = ceiling;
1551
+ warnings.push(`${key} clamped to ${ceiling}`);
1552
+ } else {
1553
+ resolved[key] = value;
1554
+ }
1555
+ } else {
1556
+ throw new TypeError(`limits.${key} must be a positive integer`);
1557
+ }
1558
+ }
1559
+ return { limits: resolved, warnings };
1560
+ }
1561
+
1562
+ function normalizeCounters(counters, context) {
1563
+ const defaults = {
1564
+ transitions: 0,
1565
+ attempts: Number(context.attemptNumber || counters.attempts || 1),
1566
+ depth: Number(context.orchestrationDepth || counters.depth || 0),
1567
+ subtasks: Number(counters.subtasks || 0),
1568
+ agents: Number(counters.agents || 0)
1569
+ };
1570
+ const normalized = {};
1571
+ for (const [key, value] of Object.entries({ ...defaults, ...counters })) {
1572
+ if (!Number.isInteger(Number(value)) || Number(value) < 0) {
1573
+ throw new TypeError(`counters.${key} must be a non-negative integer`);
1574
+ }
1575
+ normalized[key] = Number(value);
1576
+ }
1577
+ if (normalized.attempts === 0) normalized.attempts = 1;
1578
+ return normalized;
1579
+ }
1580
+
1581
+ function incrementTransitions(counters) {
1582
+ return {
1583
+ ...cloneData(counters),
1584
+ transitions: counters.transitions + 1
1585
+ };
1586
+ }
1587
+
1588
+ function normalizeDecisions(decisions) {
1589
+ return {
1590
+ ...cloneData(DEFAULT_DECISIONS),
1591
+ ...cloneData(decisions)
1592
+ };
1593
+ }
1594
+
1595
+ function createCycleSignature(input) {
1596
+ const stable = {
1597
+ classification: summarizeObject(input.decisions.classification, ['taskKind', 'complexity', 'risk', 'canDelegate']),
1598
+ manager: summarizeObject(input.decisions.manager, ['id', 'sector', 'decision']),
1599
+ engineCandidate: engineIdFrom(input.decisions.engineCandidate),
1600
+ preflight: summarizeObject(input.decisions.preflight, ['decision', 'reason']),
1601
+ budget: summarizeObject(input.decisions.budget, ['allowed', 'ok', 'reason']),
1602
+ policy: summarizeObject(input.decisions.policy, ['allowed', 'ok', 'reason']),
1603
+ gate: runtimeGateDecision(input.decisions.runtimeGate),
1604
+ subtasks: idsFrom(input.moduleResults.plannedSubtasks),
1605
+ assignments: idsFrom(input.moduleResults.assignments, 'subtaskId'),
1606
+ conflicts: sortConflicts(input.conflicts).map(conflict => ({
1607
+ type: conflict.type,
1608
+ file: redactCycleScalar(conflict.file || conflict.path || null),
1609
+ ids: conflict.subtaskIds || [conflict.subtaskId].filter(Boolean)
1610
+ })),
1611
+ consolidationCompleteness: input.moduleResults.consolidation && input.moduleResults.consolidation.consolidationCompleteness || null,
1612
+ verificationOutcome: input.moduleResults.verification && (input.moduleResults.verification.verificationOutcome || input.moduleResults.verification.outcome) || null
1613
+ };
1614
+ return [
1615
+ 'cycle',
1616
+ input.state,
1617
+ input.event,
1618
+ taskIdFrom(input.task, input.context),
1619
+ stringOrNull(input.context.parentTaskId) || 'root',
1620
+ runtimeGateDecision(input.decisions.runtimeGate) || 'none',
1621
+ canonicalStringify(input.counters),
1622
+ canonicalStringify(stable)
1623
+ ].join(':');
1624
+ }
1625
+
1626
+ function directExecutionPlan(task) {
1627
+ return {
1628
+ mode: 'direct',
1629
+ executionMode: 'serial',
1630
+ order: [String(task.id)]
1631
+ };
1632
+ }
1633
+
1634
+ function serialOrderFor(task, modules) {
1635
+ if (Array.isArray(modules.serialOrder) && modules.serialOrder.length > 0) {
1636
+ return modules.serialOrder.map(String);
1637
+ }
1638
+ if (Array.isArray(modules.plannedSubtasks) && modules.plannedSubtasks.length > 0) {
1639
+ return modules.plannedSubtasks.map(item => String(item.id));
1640
+ }
1641
+ return isValidTask(task) ? [String(task.id)] : [];
1642
+ }
1643
+
1644
+ function isValidConsolidation(value) {
1645
+ return isPlainObject(value) &&
1646
+ value.schemaVersion === 1 &&
1647
+ typeof value.taskId === 'string' &&
1648
+ ['accepted', 'accepted_with_warnings', 'rejected', 'inconclusive', 'needs_human'].includes(value.status) &&
1649
+ ['complete', 'partial', 'invalid'].includes(value.consolidationCompleteness) &&
1650
+ arrayFieldsValid(value, ['perSubtask', 'evidence', 'missingEvidence', 'warnings', 'failures', 'codeFailures', 'infrastructureFailures', 'conflicts', 'inconsistencies', 'invalidReferences', 'remainingIssues', 'inputErrors']);
1651
+ }
1652
+
1653
+ function isValidVerification(value) {
1654
+ if (!isPlainObject(value)) return false;
1655
+ if (value.mode === 'not_run') return typeof value.reason === 'string';
1656
+ const outcome = value.verificationOutcome || value.outcome;
1657
+ return value.schemaVersion === 1 &&
1658
+ ['verified', 'verified_with_warnings', 'rejected', 'inconclusive', 'blocked', 'needs_human'].includes(outcome);
1659
+ }
1660
+
1661
+ function arrayFieldsValid(value, fields) {
1662
+ return fields.every(field => value[field] === undefined || Array.isArray(value[field]));
1663
+ }
1664
+
1665
+ function recommendedNextAction(state, gate, consolidation, verification, conflicts) {
1666
+ if (state === 'loop.cancelled_limit') return 'stop_limit';
1667
+ if (state === 'loop.cancelled_cycle') return 'stop_cycle';
1668
+ if (state === 'loop.cancelled_invalid_transition' || state === 'loop.cancelled_invalid_input' || state === 'loop.failed') return 'stop_invalid';
1669
+ if (state === 'loop.needs_human') return 'request_human';
1670
+ if (state === 'loop.blocked') {
1671
+ if (runtimeGateDecision(gate) === 'FALLBACK') return 'integrate_runtime_later';
1672
+ if (conflicts.some(conflict => conflict.severity === 'critical')) return 'review_conflicts';
1673
+ return 'resolve_block';
1674
+ }
1675
+ if (state === 'loop.completed') {
1676
+ const verificationRan = isPlainObject(verification) && verification.mode !== 'not_run' && isValidVerification(verification);
1677
+ const verificationOutcomeValue = verificationRan ? verification.verificationOutcome || verification.outcome : null;
1678
+ if (!isValidConsolidation(consolidation) ||
1679
+ !verificationRan ||
1680
+ !['verified', 'verified_with_warnings'].includes(verificationOutcomeValue)) {
1681
+ return 'provide_simulated_results';
1682
+ }
1683
+ return 'execute_worker';
1684
+ }
1685
+ return 'stop_invalid';
1686
+ }
1687
+
1688
+ function reasonToTerminal(nextState, derived) {
1689
+ if (nextState === 'loop.completed') return 'completed';
1690
+ if (nextState === 'loop.blocked') return derived.event === 'GATE_FALLBACK' ? 'fallback_required' : 'blocked';
1691
+ if (nextState === 'loop.needs_human') return 'needs_human';
1692
+ if (nextState === 'loop.cancelled_invalid_input') return 'invalid_input';
1693
+ if (nextState === 'loop.failed') return 'failed';
1694
+ return null;
1695
+ }
1696
+
1697
+ function stateSummary(state) {
1698
+ return state.replace(/^loop\./, '');
1699
+ }
1700
+
1701
+ function defaultLoopId(task, context) {
1702
+ return `loop:${isValidTask(task) ? String(task.id) : 'null'}:${stringOrNull(context.parentTaskId) || 'root'}`;
1703
+ }
1704
+
1705
+ function taskIdFrom(task, context) {
1706
+ return isValidTask(task) ? String(task.id) : stringOrNull(context.taskId) || 'null';
1707
+ }
1708
+
1709
+ function getParentTaskId(task, context) {
1710
+ return stringOrNull(context.parentTaskId) || (isPlainObject(task) ? stringOrNull(task.parentTaskId) : null);
1711
+ }
1712
+
1713
+ function isValidTask(task) {
1714
+ return isPlainObject(task) && task.id !== undefined && task.id !== null && String(task.id).length > 0;
1715
+ }
1716
+
1717
+ function gateReason(gate, fallback) {
1718
+ if (!isPlainObject(gate)) return fallback;
1719
+ if (typeof gate.reason === 'string') return gate.reason;
1720
+ if (Array.isArray(gate.reasons) && gate.reasons.length > 0) return gate.reasons.map(String).join('; ');
1721
+ return fallback;
1722
+ }
1723
+
1724
+ function runtimeGateDecision(gate) {
1725
+ return isPlainObject(gate) && typeof gate.decision === 'string' ? gate.decision : null;
1726
+ }
1727
+
1728
+ function managerIdFrom(manager) {
1729
+ if (!isPlainObject(manager)) return null;
1730
+ return stringOrNull(manager.id) || stringOrNull(manager.sector) || stringOrNull(manager.managerId);
1731
+ }
1732
+
1733
+ function engineIdFrom(engine) {
1734
+ if (engine === null) return null;
1735
+ if (!isPlainObject(engine)) return null;
1736
+ return stringOrNull(engine.id) || stringOrNull(engine.selectedEngine) || stringOrNull(engine.engine);
1737
+ }
1738
+
1739
+ function idsFrom(values, key = 'id') {
1740
+ return Array.isArray(values)
1741
+ ? values.map(item => isPlainObject(item) ? stringOrNull(item[key]) : null).filter(Boolean).sort(compareStrings)
1742
+ : [];
1743
+ }
1744
+
1745
+ function summarizeObject(value, keys) {
1746
+ if (!isPlainObject(value)) return null;
1747
+ return Object.fromEntries(keys.map(key => [key, cloneData(value[key] ?? null)]));
1748
+ }
1749
+
1750
+ function hasDependencyCycle(subtasks) {
1751
+ const graph = new Map();
1752
+ for (const subtask of subtasks) {
1753
+ if (!isPlainObject(subtask) || typeof subtask.id !== 'string') continue;
1754
+ graph.set(subtask.id, Array.isArray(subtask.blockedBy) ? subtask.blockedBy.filter(id => typeof id === 'string') : []);
1755
+ }
1756
+ const visiting = new Set();
1757
+ const visited = new Set();
1758
+ const visit = id => {
1759
+ if (visiting.has(id)) return true;
1760
+ if (visited.has(id)) return false;
1761
+ visiting.add(id);
1762
+ for (const next of graph.get(id) || []) {
1763
+ if (visit(next)) return true;
1764
+ }
1765
+ visiting.delete(id);
1766
+ visited.add(id);
1767
+ return false;
1768
+ };
1769
+ return [...graph.keys()].some(visit);
1770
+ }
1771
+
1772
+ function hasWriteFiles(subtask) {
1773
+ if (!isPlainObject(subtask)) return false;
1774
+ if (subtask.requires && subtask.requires.filesystemWrite === false) return false;
1775
+ return true;
1776
+ }
1777
+
1778
+ function isSensitivePath(value) {
1779
+ const text = String(value).replace(/\\/g, '/').toLowerCase();
1780
+ return text === '.env' ||
1781
+ text.endsWith('/.env') ||
1782
+ text.includes('/.env/') ||
1783
+ text.includes('secret') ||
1784
+ text.includes('token') ||
1785
+ text.includes('credential') ||
1786
+ text.includes('password') ||
1787
+ text.includes('authorization') ||
1788
+ text.includes('cookie') ||
1789
+ text.includes('apikey') ||
1790
+ text.includes('api-key') ||
1791
+ text.startsWith('../') ||
1792
+ text.includes('/../') ||
1793
+ text.startsWith('/') ||
1794
+ /^[a-z]:\//i.test(text);
1795
+ }
1796
+
1797
+ function normalizePathForCompare(value) {
1798
+ return String(value || '').replace(/\\/g, '/').replace(/\/+$/g, '').toLowerCase();
1799
+ }
1800
+
1801
+ function extractConflicts(moduleResults, context) {
1802
+ const conflicts = firstDefined(moduleResults && moduleResults.conflicts, context && context.conflicts);
1803
+ return Array.isArray(conflicts) ? sortConflicts(conflicts) : [];
1804
+ }
1805
+
1806
+ function sortConflicts(conflicts) {
1807
+ return (Array.isArray(conflicts) ? conflicts : [])
1808
+ .map(item => cloneData(item))
1809
+ .sort((a, b) => compareStrings(severityRank(a.severity), severityRank(b.severity)) ||
1810
+ compareStrings(a.type || '', b.type || '') ||
1811
+ compareStrings(a.file || a.path || '', b.file || b.path || '') ||
1812
+ compareStrings(canonicalStringify(a.subtaskIds || a.subtaskId || ''), canonicalStringify(b.subtaskIds || b.subtaskId || '')));
1813
+ }
1814
+
1815
+ function severityRank(severity) {
1816
+ return { critical: '0', high: '1', medium: '2', low: '3' }[severity] || '9';
1817
+ }
1818
+
1819
+ function loopError(code, message, state, event, field, severity, provenanceValue = null) {
1820
+ return {
1821
+ code,
1822
+ message,
1823
+ state: state || null,
1824
+ event: event || null,
1825
+ field: field || null,
1826
+ severity,
1827
+ provenance: provenanceValue
1828
+ };
1829
+ }
1830
+
1831
+ function sortErrors(errors) {
1832
+ return (Array.isArray(errors) ? errors : []).map(item => cloneData(item)).sort((a, b) =>
1833
+ compareStrings(a.severity || '', b.severity || '') ||
1834
+ compareStrings(a.code || '', b.code || '') ||
1835
+ compareStrings(a.message || '', b.message || '')
1836
+ );
1837
+ }
1838
+
1839
+ function provenance(source, adapter, field, simulated) {
1840
+ return {
1841
+ source,
1842
+ adapter,
1843
+ field,
1844
+ simulated
1845
+ };
1846
+ }
1847
+
1848
+ function firstDefined(...values) {
1849
+ for (const value of values) {
1850
+ if (value !== undefined) return value;
1851
+ }
1852
+ return undefined;
1853
+ }
1854
+
1855
+ function uniqueMessages(values) {
1856
+ return [...new Set((Array.isArray(values) ? values : []).filter(value => typeof value === 'string'))].sort(compareStrings);
1857
+ }
1858
+
1859
+ function uniqueSortedStrings(values) {
1860
+ return [...new Set((Array.isArray(values) ? values : []).map(value => String(value)).filter(Boolean))].sort(compareStrings);
1861
+ }
1862
+
1863
+ function assertPlainApiObject(value, name) {
1864
+ if (!isPlainObject(value)) {
1865
+ throw new TypeError(`${name} must be an object`);
1866
+ }
1867
+ }
1868
+
1869
+ function isPlainObject(value) {
1870
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
1871
+ }
1872
+
1873
+ function isThenable(value) {
1874
+ return value !== null && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function';
1875
+ }
1876
+
1877
+ function stringOrNull(value) {
1878
+ return typeof value === 'string' && value.length > 0 ? value : null;
1879
+ }
1880
+
1881
+ function errorMessage(error) {
1882
+ return error && typeof error.message === 'string' ? error.message : String(error);
1883
+ }
1884
+
1885
+ function redactSensitive(value, seen = new WeakMap(), parentKey = '') {
1886
+ if (Array.isArray(value)) {
1887
+ if (seen.has(value)) return '[Circular]';
1888
+ seen.set(value, true);
1889
+ return value.map(item => redactSensitive(item, seen, parentKey));
1890
+ }
1891
+ if (isPlainObject(value)) {
1892
+ if (seen.has(value)) return '[Circular]';
1893
+ seen.set(value, true);
1894
+ return Object.fromEntries(Object.keys(value).sort(compareStrings).map(key => {
1895
+ const sensitive = SENSITIVE_KEYS.some(fragment => key.toLowerCase().includes(fragment));
1896
+ return [key, sensitive ? '[REDACTED]' : redactSensitive(value[key], seen, key)];
1897
+ }));
1898
+ }
1899
+ if (typeof value === 'string' && isSensitiveTraceString(value)) {
1900
+ return '[REDACTED]';
1901
+ }
1902
+ return parentKey && SENSITIVE_KEYS.some(fragment => parentKey.toLowerCase().includes(fragment)) ? '[REDACTED]' : value;
1903
+ }
1904
+
1905
+ function isSensitiveTraceString(value) {
1906
+ const text = value.replace(/\\/g, '/').toLowerCase();
1907
+ return text === '.env' ||
1908
+ text.endsWith('/.env') ||
1909
+ text.includes('/.env/') ||
1910
+ text.includes('secret') ||
1911
+ text.includes('token') ||
1912
+ text.startsWith('../') ||
1913
+ text.includes('/../') ||
1914
+ text.startsWith('/') ||
1915
+ /^[a-z]:\//i.test(text);
1916
+ }
1917
+
1918
+ function redactCycleScalar(value) {
1919
+ if (typeof value === 'string' && isSensitiveTraceString(value)) {
1920
+ return '[REDACTED]';
1921
+ }
1922
+ return value;
1923
+ }
1924
+
1925
+ function cloneData(value, seen = new WeakMap()) {
1926
+ if (Array.isArray(value)) {
1927
+ if (seen.has(value)) return '[Circular]';
1928
+ seen.set(value, true);
1929
+ return value.map(item => cloneData(item, seen));
1930
+ }
1931
+ if (isPlainObject(value)) {
1932
+ if (seen.has(value)) return '[Circular]';
1933
+ seen.set(value, true);
1934
+ return Object.fromEntries(Object.keys(value).sort(compareStrings).map(key => [key, cloneData(value[key], seen)]));
1935
+ }
1936
+ if (typeof value === 'bigint') {
1937
+ return `${value.toString()}n`;
1938
+ }
1939
+ if (typeof value === 'function' || typeof value === 'symbol') {
1940
+ return String(value);
1941
+ }
1942
+ return value;
1943
+ }
1944
+
1945
+ function canonicalStringify(value, seen = new WeakMap()) {
1946
+ if (Array.isArray(value)) {
1947
+ if (seen.has(value)) return '"[Circular]"';
1948
+ seen.set(value, true);
1949
+ return `[${value.map(item => canonicalStringify(item, seen)).join(',')}]`;
1950
+ }
1951
+ if (isPlainObject(value)) {
1952
+ if (seen.has(value)) return '"[Circular]"';
1953
+ seen.set(value, true);
1954
+ return `{${Object.keys(value).sort(compareStrings).map(key => `${JSON.stringify(key)}:${canonicalStringify(value[key], seen)}`).join(',')}}`;
1955
+ }
1956
+ if (typeof value === 'bigint') {
1957
+ return JSON.stringify(`${value.toString()}n`);
1958
+ }
1959
+ if (typeof value === 'function' || typeof value === 'symbol') {
1960
+ return JSON.stringify(String(value));
1961
+ }
1962
+ return JSON.stringify(value);
1963
+ }
1964
+
1965
+ function compareStrings(a, b) {
1966
+ const left = String(a);
1967
+ const right = String(b);
1968
+ if (left < right) return -1;
1969
+ if (left > right) return 1;
1970
+ return 0;
1971
+ }