@probelabs/visor 0.1.124 → 0.1.126

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 (195) hide show
  1. package/dist/config.d.ts.map +1 -1
  2. package/dist/docs/DEPLOYMENT.md +117 -11
  3. package/dist/docs/GITHUB_CHECKS.md +18 -4
  4. package/dist/docs/NPM_USAGE.md +112 -39
  5. package/dist/docs/action-reference.md +63 -9
  6. package/dist/docs/advanced-ai.md +58 -51
  7. package/dist/docs/ai-configuration.md +99 -11
  8. package/dist/docs/ai-custom-tools-usage.md +70 -33
  9. package/dist/docs/ai-custom-tools.md +50 -27
  10. package/dist/docs/architecture.md +1232 -0
  11. package/dist/docs/bot-transports-rfc.md +13 -3
  12. package/dist/docs/ci-cli-mode.md +116 -8
  13. package/dist/docs/claude-code.md +111 -41
  14. package/dist/docs/command-provider.md +37 -15
  15. package/dist/docs/commands.md +252 -6
  16. package/dist/docs/configuration.md +138 -4
  17. package/dist/docs/contributing.md +737 -0
  18. package/dist/docs/custom-tools.md +39 -8
  19. package/dist/docs/dashboards/README.md +33 -19
  20. package/dist/docs/debug-visualizer-progress.md +14 -13
  21. package/dist/docs/debug-visualizer-rfc.md +14 -13
  22. package/dist/docs/debug-visualizer.md +30 -5
  23. package/dist/docs/debugging.md +73 -8
  24. package/dist/docs/default-output-schema.md +24 -20
  25. package/dist/docs/dependencies.md +75 -21
  26. package/dist/docs/dev-playbook.md +85 -9
  27. package/dist/docs/engine-pause-resume-rfc.md +11 -11
  28. package/dist/docs/engine-state-machine-plan.md +10 -3
  29. package/dist/docs/event-driven-github-integration-rfc.md +20 -11
  30. package/dist/docs/event-triggers.md +95 -6
  31. package/dist/docs/execution-statistics-rfc.md +16 -4
  32. package/dist/docs/fact-validator-gap-analysis.md +12 -1
  33. package/dist/docs/fact-validator-implementation-plan.md +19 -11
  34. package/dist/docs/fail-if.md +116 -11
  35. package/dist/docs/failure-conditions-implementation.md +40 -6
  36. package/dist/docs/failure-conditions-schema.md +243 -87
  37. package/dist/docs/failure-routing-rfc.md +43 -18
  38. package/dist/docs/failure-routing.md +80 -23
  39. package/dist/docs/faq.md +836 -0
  40. package/dist/docs/foreach-dependency-propagation.md +32 -15
  41. package/dist/docs/github-ops.md +6 -5
  42. package/dist/docs/glossary.md +322 -0
  43. package/dist/docs/goto-forward-run-plan.md +23 -10
  44. package/dist/docs/guides/criticality-modes.md +15 -13
  45. package/dist/docs/guides/fault-management-and-contracts.md +8 -5
  46. package/dist/docs/guides/workflow-style-guide.md +17 -8
  47. package/dist/docs/http.md +102 -3
  48. package/dist/docs/human-input-provider.md +20 -36
  49. package/dist/docs/index.md +206 -0
  50. package/dist/docs/lifecycle-hooks.md +322 -2
  51. package/dist/docs/limits.md +20 -5
  52. package/dist/docs/liquid-templates.md +86 -14
  53. package/dist/docs/loop-routing-refactor.md +4 -2
  54. package/dist/docs/mcp-provider.md +53 -19
  55. package/dist/docs/mcp.md +27 -1
  56. package/dist/docs/memory.md +7 -2
  57. package/dist/docs/migration.md +596 -0
  58. package/dist/docs/observability.md +227 -6
  59. package/dist/docs/output-formats.md +388 -9
  60. package/dist/docs/output-history.md +36 -6
  61. package/dist/docs/performance.md +510 -4
  62. package/dist/docs/pluggable.md +95 -4
  63. package/dist/docs/proposals/snapshot-scope-execution.md +6 -5
  64. package/dist/docs/providers/git-checkout.md +16 -14
  65. package/dist/docs/providers/noop.md +696 -0
  66. package/dist/docs/recipes.md +8 -9
  67. package/dist/docs/rfc/git-checkout-step.md +3 -1
  68. package/dist/docs/rfc/on_init-hook.md +18 -5
  69. package/dist/docs/rfc/workspace-isolation.md +16 -0
  70. package/dist/docs/roadmap/criticality-implementation-tasks.md +27 -27
  71. package/dist/docs/router-patterns.md +155 -43
  72. package/dist/docs/schema-templates.md +51 -15
  73. package/dist/docs/script.md +162 -13
  74. package/dist/docs/sdk.md +46 -12
  75. package/dist/docs/security.md +464 -5
  76. package/dist/docs/slack-integration.md +481 -0
  77. package/dist/docs/tag-filtering.md +60 -20
  78. package/dist/docs/telemetry-setup.md +157 -46
  79. package/dist/docs/test-framework-rfc.md +37 -36
  80. package/dist/docs/testing/assertions.md +92 -4
  81. package/dist/docs/testing/ci.md +56 -7
  82. package/dist/docs/testing/cli.md +57 -15
  83. package/dist/docs/testing/cookbook.md +53 -20
  84. package/dist/docs/testing/dsl-reference.md +110 -9
  85. package/dist/docs/testing/fixtures-and-mocks.md +28 -3
  86. package/dist/docs/testing/flows.md +59 -4
  87. package/dist/docs/testing/getting-started.md +14 -13
  88. package/dist/docs/testing/troubleshooting.md +39 -2
  89. package/dist/docs/timeouts.md +174 -18
  90. package/dist/docs/troubleshooting.md +176 -6
  91. package/dist/docs/workflow-creation-guide.md +101 -3
  92. package/dist/docs/workflows.md +138 -41
  93. package/dist/examples/README.md +169 -4
  94. package/dist/examples/ai-custom-tools-simple.yaml +2 -3
  95. package/dist/examples/cron-webhook-config.yaml +15 -0
  96. package/dist/examples/forEach-example.yaml +6 -0
  97. package/dist/examples/git-checkout-basic.yaml +4 -0
  98. package/dist/examples/git-checkout-compare.yaml +6 -0
  99. package/dist/examples/git-checkout-cross-repo.yaml +7 -0
  100. package/dist/examples/http-integration-config.yaml +30 -0
  101. package/dist/examples/https-server-config.yaml +15 -0
  102. package/dist/examples/mcp-provider-example.yaml +10 -10
  103. package/dist/examples/transform-example.yaml +3 -0
  104. package/dist/examples/webhook-pipeline-config.yaml +18 -0
  105. package/dist/examples/workflows/workflow-composition-example.yaml +4 -0
  106. package/dist/frontends/slack-frontend.d.ts +2 -0
  107. package/dist/frontends/slack-frontend.d.ts.map +1 -1
  108. package/dist/generated/config-schema.d.ts +11 -7
  109. package/dist/generated/config-schema.d.ts.map +1 -1
  110. package/dist/generated/config-schema.json +11 -7
  111. package/dist/index.js +3127 -974
  112. package/dist/output/traces/{run-2026-01-28T16-15-24-569Z.ndjson → run-2026-01-31T16-37-22-321Z.ndjson} +84 -84
  113. package/dist/output/traces/{run-2026-01-28T16-16-09-757Z.ndjson → run-2026-01-31T16-38-06-031Z.ndjson} +1013 -1013
  114. package/dist/providers/ai-check-provider.d.ts +9 -2
  115. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  116. package/dist/providers/command-check-provider.d.ts.map +1 -1
  117. package/dist/providers/mcp-custom-sse-server.d.ts +17 -1
  118. package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -1
  119. package/dist/providers/workflow-check-provider.d.ts.map +1 -1
  120. package/dist/providers/workflow-tool-executor.d.ts +68 -0
  121. package/dist/providers/workflow-tool-executor.d.ts.map +1 -0
  122. package/dist/sdk/{check-provider-registry-AQ3JETBG.mjs → check-provider-registry-3KI5RKXT.mjs} +6 -5
  123. package/dist/sdk/check-provider-registry-IYILYY35.mjs +28 -0
  124. package/dist/sdk/chunk-2CPMMNIX.mjs +1459 -0
  125. package/dist/sdk/chunk-2CPMMNIX.mjs.map +1 -0
  126. package/dist/sdk/chunk-5LI6T4O3.mjs +3600 -0
  127. package/dist/sdk/chunk-5LI6T4O3.mjs.map +1 -0
  128. package/dist/sdk/{chunk-YLQ4UN62.mjs → chunk-A4PGHURG.mjs} +6838 -6257
  129. package/dist/sdk/chunk-A4PGHURG.mjs.map +1 -0
  130. package/dist/sdk/chunk-EXFGO4FX.mjs +147 -0
  131. package/dist/sdk/chunk-EXFGO4FX.mjs.map +1 -0
  132. package/dist/sdk/chunk-PJ7K5UFC.mjs +17732 -0
  133. package/dist/sdk/chunk-PJ7K5UFC.mjs.map +1 -0
  134. package/dist/sdk/{chunk-BHZ4CKUS.mjs → chunk-PXFIALUH.mjs} +77 -8
  135. package/dist/sdk/chunk-PXFIALUH.mjs.map +1 -0
  136. package/dist/sdk/{chunk-PVITVJ6J.mjs → chunk-RTKJXNZS.mjs} +32 -9
  137. package/dist/sdk/chunk-RTKJXNZS.mjs.map +1 -0
  138. package/dist/sdk/chunk-VW2GBXQT.mjs +606 -0
  139. package/dist/sdk/chunk-VW2GBXQT.mjs.map +1 -0
  140. package/dist/sdk/{config-RQQPMLRD.mjs → config-5AUYQFHE.mjs} +2 -2
  141. package/dist/sdk/config-6CUVEH7H.mjs +16 -0
  142. package/dist/sdk/config-6CUVEH7H.mjs.map +1 -0
  143. package/dist/sdk/{github-frontend-6Q4BISZX.mjs → github-frontend-BZ4N3BFZ.mjs} +7 -3
  144. package/dist/sdk/github-frontend-BZ4N3BFZ.mjs.map +1 -0
  145. package/dist/sdk/host-4MT3EW2I.mjs +52 -0
  146. package/dist/sdk/{host-P5NQICP7.mjs → host-NYWXLIFC.mjs} +2 -2
  147. package/dist/sdk/host-NYWXLIFC.mjs.map +1 -0
  148. package/dist/sdk/{routing-DEY2AIXM.mjs → routing-6R42GXUO.mjs} +2 -2
  149. package/dist/sdk/routing-6R42GXUO.mjs.map +1 -0
  150. package/dist/sdk/routing-7FXPULTO.mjs +24 -0
  151. package/dist/sdk/routing-7FXPULTO.mjs.map +1 -0
  152. package/dist/sdk/sdk.d.mts +3 -1
  153. package/dist/sdk/sdk.d.ts +3 -1
  154. package/dist/sdk/sdk.js +12163 -11204
  155. package/dist/sdk/sdk.js.map +1 -1
  156. package/dist/sdk/sdk.mjs +14 -10
  157. package/dist/sdk/sdk.mjs.map +1 -1
  158. package/dist/sdk/slack-frontend-JUT3TYVC.mjs +821 -0
  159. package/dist/sdk/slack-frontend-JUT3TYVC.mjs.map +1 -0
  160. package/dist/sdk/workflow-check-provider-H3CUOLUD.mjs +28 -0
  161. package/dist/sdk/workflow-check-provider-H3CUOLUD.mjs.map +1 -0
  162. package/dist/sdk/workflow-check-provider-YUNNF4KC.mjs +28 -0
  163. package/dist/sdk/workflow-check-provider-YUNNF4KC.mjs.map +1 -0
  164. package/dist/sdk/workflow-registry-KFWSDSLM.mjs +12 -0
  165. package/dist/sdk/workflow-registry-KFWSDSLM.mjs.map +1 -0
  166. package/dist/slack/socket-runner.d.ts +2 -0
  167. package/dist/slack/socket-runner.d.ts.map +1 -1
  168. package/dist/state-machine/context/workflow-inputs.d.ts +20 -0
  169. package/dist/state-machine/context/workflow-inputs.d.ts.map +1 -0
  170. package/dist/state-machine/dispatch/execution-invoker.d.ts.map +1 -1
  171. package/dist/state-machine/dispatch/foreach-processor.d.ts.map +1 -1
  172. package/dist/state-machine/dispatch/stats-manager.d.ts.map +1 -1
  173. package/dist/state-machine/states/level-dispatch.d.ts.map +1 -1
  174. package/dist/state-machine/states/routing.d.ts +2 -1
  175. package/dist/state-machine/states/routing.d.ts.map +1 -1
  176. package/dist/traces/{run-2026-01-28T16-15-24-569Z.ndjson → run-2026-01-31T16-37-22-321Z.ndjson} +84 -84
  177. package/dist/traces/{run-2026-01-28T16-16-09-757Z.ndjson → run-2026-01-31T16-38-06-031Z.ndjson} +1013 -1013
  178. package/dist/types/config.d.ts +3 -1
  179. package/dist/types/config.d.ts.map +1 -1
  180. package/dist/utils/human-id.d.ts +12 -0
  181. package/dist/utils/human-id.d.ts.map +1 -0
  182. package/dist/utils/worktree-manager.d.ts +3 -0
  183. package/dist/utils/worktree-manager.d.ts.map +1 -1
  184. package/dist/workflow-executor.d.ts.map +1 -1
  185. package/dist/workflow-registry.d.ts +1 -0
  186. package/dist/workflow-registry.d.ts.map +1 -1
  187. package/package.json +2 -2
  188. package/dist/sdk/chunk-BHZ4CKUS.mjs.map +0 -1
  189. package/dist/sdk/chunk-PVITVJ6J.mjs.map +0 -1
  190. package/dist/sdk/chunk-YLQ4UN62.mjs.map +0 -1
  191. package/dist/sdk/github-frontend-6Q4BISZX.mjs.map +0 -1
  192. /package/dist/sdk/{check-provider-registry-AQ3JETBG.mjs.map → check-provider-registry-3KI5RKXT.mjs.map} +0 -0
  193. /package/dist/sdk/{config-RQQPMLRD.mjs.map → check-provider-registry-IYILYY35.mjs.map} +0 -0
  194. /package/dist/sdk/{routing-DEY2AIXM.mjs.map → config-5AUYQFHE.mjs.map} +0 -0
  195. /package/dist/sdk/{host-P5NQICP7.mjs.map → host-4MT3EW2I.mjs.map} +0 -0
@@ -1,6 +1,13 @@
1
1
  # Lifecycle Hooks
2
2
 
3
- Lifecycle hooks allow you to execute preprocessing or setup tasks automatically before a step runs. This is particularly useful for enriching context, fetching external data, or preparing the environment.
3
+ Visor provides four lifecycle hooks that allow you to control step execution at different phases:
4
+
5
+ | Hook | When it Runs | Use Case |
6
+ |------|--------------|----------|
7
+ | `on_init` | **Before** step execution | Preprocessing, data fetching, context enrichment |
8
+ | `on_success` | **After** step succeeds | Post-processing, notifications, routing to next step |
9
+ | `on_fail` | **After** step fails | Error handling, retries, remediation |
10
+ | `on_finish` | **After all** forEach iterations complete | Aggregation, validation across all items |
4
11
 
5
12
  ## `on_init` Hook
6
13
 
@@ -245,9 +252,322 @@ See the `examples/` directory for comprehensive examples:
245
252
  4. **Use `run_js` for conditionals**: Avoid fetching unnecessary data
246
253
  5. **Handle failures gracefully**: Consider what happens if preprocessing fails
247
254
 
248
- ### See Also
255
+ ---
256
+
257
+ ## `on_success` Hook
258
+
259
+ The `on_success` hook runs **after** a step completes successfully. It allows you to:
260
+ - Run post-processing steps
261
+ - Trigger notifications or downstream actions
262
+ - Jump back to a previous step for re-evaluation (routing)
263
+
264
+ ### Basic Usage
265
+
266
+ ```yaml
267
+ steps:
268
+ build:
269
+ type: command
270
+ exec: npm run build
271
+ on_success:
272
+ run: [notify, deploy]
273
+ ```
274
+
275
+ ### Configuration Options
276
+
277
+ ```yaml
278
+ on_success:
279
+ # Run additional steps after success
280
+ run: [step1, step2]
281
+
282
+ # Optional: jump back to an ancestor step
283
+ goto: previous-step
284
+
285
+ # Optional: simulate a different event during goto
286
+ goto_event: pr_updated
287
+
288
+ # Dynamic step selection (JS expression returning string[])
289
+ run_js: |
290
+ return outputs['build'].hasWarnings ? ['review-warnings'] : [];
291
+
292
+ # Dynamic routing (JS expression returning step id or null)
293
+ goto_js: |
294
+ // Re-run once using history length as attempt counter
295
+ return outputs.history['build'].length === 1 ? 'setup' : null;
296
+
297
+ # Declarative transitions (evaluated in order, first match wins)
298
+ transitions:
299
+ - when: "outputs['build'].score >= 90"
300
+ to: publish
301
+ - when: "outputs['build'].score >= 70"
302
+ to: review
303
+ - when: "true"
304
+ to: null # No routing
305
+ ```
306
+
307
+ ### Example: Conditional Post-Processing
308
+
309
+ ```yaml
310
+ steps:
311
+ analyze:
312
+ type: ai
313
+ prompt: Analyze code quality
314
+ on_success:
315
+ run_js: |
316
+ const result = outputs['analyze'];
317
+ if (result.issues?.length > 0) {
318
+ return ['create-report', 'notify-team'];
319
+ }
320
+ return ['mark-approved'];
321
+
322
+ create-report:
323
+ type: command
324
+ exec: generate-report.sh
325
+ on: []
326
+
327
+ notify-team:
328
+ type: http
329
+ url: https://slack.webhook.url
330
+ on: []
331
+
332
+ mark-approved:
333
+ type: command
334
+ exec: gh pr review --approve
335
+ on: []
336
+ ```
337
+
338
+ ---
339
+
340
+ ## `on_fail` Hook
341
+
342
+ The `on_fail` hook runs **after** a step fails. It provides mechanisms for:
343
+ - Automatic retries with backoff
344
+ - Running remediation steps before retry
345
+ - Jumping back to an ancestor step for re-execution
346
+
347
+ ### Basic Usage
348
+
349
+ ```yaml
350
+ steps:
351
+ deploy:
352
+ type: command
353
+ exec: ./deploy.sh
354
+ on_fail:
355
+ retry:
356
+ max: 3
357
+ backoff:
358
+ mode: exponential
359
+ delay_ms: 1000
360
+ ```
361
+
362
+ ### Configuration Options
363
+
364
+ ```yaml
365
+ on_fail:
366
+ # Retry configuration
367
+ retry:
368
+ max: 3 # Maximum retry attempts
369
+ backoff:
370
+ mode: fixed | exponential # Backoff strategy
371
+ delay_ms: 1000 # Initial delay
372
+
373
+ # Run remediation steps before retry
374
+ run: [cleanup, reset-state]
375
+
376
+ # Jump back to ancestor step
377
+ goto: setup
378
+
379
+ # Simulate different event during goto
380
+ goto_event: pr_updated
381
+
382
+ # Dynamic remediation (JS returning string[])
383
+ run_js: |
384
+ if (output.error?.includes('lock')) {
385
+ return ['clear-locks'];
386
+ }
387
+ return [];
388
+
389
+ # Dynamic routing (JS returning step id or null)
390
+ goto_js: |
391
+ return attempt < 2 ? 'install-deps' : null;
392
+
393
+ # Declarative transitions
394
+ transitions:
395
+ - when: "output.error?.includes('timeout')"
396
+ to: null # Don't route, just retry
397
+ - when: "output.error?.includes('auth')"
398
+ to: refresh-auth
399
+ ```
400
+
401
+ ### Example: Remediation with Retry
402
+
403
+ ```yaml
404
+ steps:
405
+ install:
406
+ type: command
407
+ exec: npm ci
408
+
409
+ test:
410
+ type: command
411
+ depends_on: [install]
412
+ exec: npm test
413
+ on_fail:
414
+ run: [clean-cache]
415
+ retry:
416
+ max: 2
417
+ backoff:
418
+ mode: fixed
419
+ delay_ms: 500
420
+
421
+ clean-cache:
422
+ type: command
423
+ exec: rm -rf node_modules/.cache
424
+ on: [] # Helper step only
425
+ ```
426
+
427
+ ---
428
+
429
+ ## `on_finish` Hook
430
+
431
+ The `on_finish` hook runs **once** after a `forEach` step completes **all** iterations and all dependent checks. This is ideal for:
432
+ - Aggregating results from all forEach iterations
433
+ - Making routing decisions based on collective outcomes
434
+ - Validation across all processed items
435
+
436
+ **Note:** `on_finish` only applies to steps with `forEach: true`.
437
+
438
+ ### When It Triggers
439
+
440
+ 1. The forEach step produces an array of items
441
+ 2. All dependent steps execute for each item
442
+ 3. After ALL iterations complete, `on_finish` triggers once
443
+
444
+ ### Basic Usage
445
+
446
+ ```yaml
447
+ steps:
448
+ process-files:
449
+ type: command
450
+ exec: "echo '[\"/a.ts\", \"/b.ts\", \"/c.ts\"]'"
451
+ forEach: true
452
+ on_finish:
453
+ run: [summarize-results]
454
+ goto_js: |
455
+ const results = outputs.history['validate-file'];
456
+ const allValid = results.every(r => r.valid);
457
+ return allValid ? null : 'process-files'; # Retry if any failed
458
+
459
+ validate-file:
460
+ type: ai
461
+ depends_on: [process-files]
462
+ prompt: Validate {{ outputs['process-files'] }}
463
+
464
+ summarize-results:
465
+ type: script
466
+ content: |
467
+ const results = outputs.history['validate-file'];
468
+ return {
469
+ total: results.length,
470
+ passed: results.filter(r => r.valid).length
471
+ };
472
+ on: []
473
+ ```
474
+
475
+ ### Available Context
476
+
477
+ The `on_finish` context is richer than other hooks:
478
+
479
+ ```javascript
480
+ {
481
+ step: { id: 'process-files', tags: [...] },
482
+ attempt: 1, // Current attempt number
483
+ loop: 0, // Current loop in routing
484
+ outputs: {
485
+ 'process-files': [...], // Array of forEach items
486
+ 'validate-file': [...], // ALL dependent results
487
+ history: { ... } // Alias for outputs_history
488
+ },
489
+ outputs_history: {
490
+ 'process-files': [[...], ...],
491
+ 'validate-file': [[...], ...],
492
+ },
493
+ outputs_raw: {
494
+ 'process-files': [...], // Aggregate/parent values
495
+ },
496
+ forEach: {
497
+ items: 3, // Number of items
498
+ last_wave_size: 3,
499
+ last_items: [...],
500
+ is_parent: true
501
+ },
502
+ memory: { get, set, has, getAll, increment, clear },
503
+ pr: { number, title, author, branch, base },
504
+ files: [...],
505
+ env: { ... },
506
+ event: { name: '...' }
507
+ }
508
+ ```
509
+
510
+ ### Example: Validation with Retry
511
+
512
+ ```yaml
513
+ steps:
514
+ extract-facts:
515
+ type: ai
516
+ forEach: true
517
+ transform_js: JSON.parse(output).facts
518
+ on_finish:
519
+ run: [aggregate-validations]
520
+ goto_js: |
521
+ const allValid = memory.get('all_valid', 'validation');
522
+ const attempt = memory.get('attempt', 'validation') || 0;
523
+
524
+ if (allValid || attempt >= 2) {
525
+ return null; // Success or max attempts
526
+ }
527
+
528
+ memory.increment('attempt', 1, 'validation');
529
+ return 'generate-response'; # Retry from ancestor
530
+
531
+ validate-fact:
532
+ type: ai
533
+ depends_on: [extract-facts]
534
+ prompt: Validate this fact...
535
+
536
+ aggregate-validations:
537
+ type: script
538
+ content: |
539
+ const results = outputs.history['validate-fact'];
540
+ const allValid = results.every(r => r.is_valid);
541
+ memory.set('all_valid', allValid, 'validation');
542
+ return { total: results.length, valid: results.filter(r => r.is_valid).length };
543
+ on: []
544
+ ```
545
+
546
+ ---
547
+
548
+ ## Loop Protection & Safety
549
+
550
+ All routing hooks (`on_success`, `on_fail`, `on_finish`) are subject to loop protection:
551
+
552
+ ```yaml
553
+ routing:
554
+ max_loops: 10 # Per-scope cap on routing transitions
555
+ ```
556
+
557
+ - **Retry counters**: Each step tracks attempt count independently
558
+ - **Loop budget**: Total routing transitions (goto + run) are capped per scope
559
+ - **forEach isolation**: Each item has its own loop/attempt counters
560
+
561
+ For hard caps on step executions, see [Execution Limits](./limits.md).
562
+
563
+ ---
564
+
565
+ ## See Also
249
566
 
567
+ - [Failure Routing](./failure-routing.md) - Complete guide to on_success, on_fail, on_finish
250
568
  - [Custom Tools](./custom-tools.md) - Define reusable MCP tools
251
569
  - [Workflows](./workflows.md) - Create reusable workflows
252
570
  - [Liquid Templates](./liquid-templates.md) - Template syntax for dynamic values
571
+ - [Output History](./output-history.md) - Accessing historical outputs in routing
572
+ - [Execution Limits](./limits.md) - Configuring execution caps
253
573
  - [RFC: on_init Hook](./rfc/on_init-hook.md) - Design proposal and rationale
@@ -9,13 +9,14 @@ This feature protects workflows from accidental infinite loops by capping how ma
9
9
 
10
10
  ### Configuration
11
11
 
12
- Global (default is 50 if omitted):
12
+ Global limits (defaults shown):
13
13
 
14
14
  ```yaml
15
15
  version: "1.0"
16
16
 
17
17
  limits:
18
- max_runs_per_check: 50 # Applies to every step unless overridden
18
+ max_runs_per_check: 50 # Applies to every step unless overridden
19
+ max_workflow_depth: 3 # Maximum nesting depth for nested workflows
19
20
  ```
20
21
 
21
22
  Per-step override:
@@ -47,9 +48,23 @@ steps:
47
48
 
48
49
  ### How this differs from `routing.max_loops`
49
50
 
50
- - `routing.max_loops` caps routing transitions (e.g., goto/retry waves) per scope.
51
- - `limits.max_runs_per_check` caps actual step executions per step (also per scope for `forEach`).
52
- - Both guard rails can be used together: set a modest routing budget (e.g., 5–10) and leave the execution cap at the default (50) or tailor per step.
51
+ - `routing.max_loops` caps routing transitions (e.g., goto/retry waves) per scope. Default: 10.
52
+ - `limits.max_runs_per_check` caps actual step executions per step (also per scope for `forEach`). Default: 50.
53
+ - `limits.max_workflow_depth` caps nested workflow invocations. Default: 3.
54
+ - Both execution and routing guard rails can be used together: set a modest routing budget (e.g., 5-10) and leave the execution cap at the default (50) or tailor per step.
55
+
56
+ ### Workflow Depth Limit
57
+
58
+ The `max_workflow_depth` setting prevents infinite recursion when workflows call other workflows:
59
+
60
+ ```yaml
61
+ limits:
62
+ max_workflow_depth: 3 # Maximum nesting depth (default: 3)
63
+ ```
64
+
65
+ When the depth limit is exceeded, the workflow provider throws an error. This protects against:
66
+ - Accidentally creating recursive workflow chains
67
+ - Runaway nested workflow invocations
53
68
 
54
69
  ### Recommendations
55
70
 
@@ -103,6 +103,20 @@ The `json` filter serializes objects to JSON strings, useful for debugging or pa
103
103
  # Debug output object
104
104
  {{ outputs | json }}
105
105
 
106
+ # Debug specific check output
107
+ {{ outputs.security | json }}
108
+
109
+ # Pass to command safely
110
+ echo '{{ pr | json }}' | jq .
111
+
112
+ # Create JSON payload
113
+ {
114
+ "title": {{ pr.title | json }},
115
+ "files": {{ files | json }},
116
+ "outputs": {{ outputs | json }}
117
+ }
118
+ ```
119
+
106
120
  ### Author Permission Filters
107
121
 
108
122
  > **📖 For complete documentation, see [Author Permissions Guide](./author-permissions.md)**
@@ -165,20 +179,6 @@ Ticket key: {{ outputs['fetch-tickets'].tickets[0].key }}
165
179
 
166
180
  If the underlying value is plain text, it behaves as a normal string.
167
181
 
168
- # Debug specific check output
169
- {{ outputs.security | json }}
170
-
171
- # Pass to command safely
172
- echo '{{ pr | json }}' | jq .
173
-
174
- # Create JSON payload
175
- {
176
- "title": {{ pr.title | json }},
177
- "files": {{ files | json }},
178
- "outputs": {{ outputs | json }}
179
- }
180
- ```
181
-
182
182
  ### String Filters
183
183
 
184
184
  ```liquid
@@ -198,6 +198,78 @@ echo '{{ pr | json }}' | jq .
198
198
  {{ files | map: "filename" }} # Array of filenames
199
199
  ```
200
200
 
201
+ ### Encoding Filters
202
+
203
+ ```liquid
204
+ # Base64 encoding/decoding
205
+ {{ "user:password" | base64 }} # Encode to base64
206
+ {{ encoded_value | base64_decode }} # Decode from base64
207
+
208
+ # JSON escaping (for use inside JSON string values)
209
+ "jql": "{{ myValue | json_escape }}"
210
+ ```
211
+
212
+ ### Shell Escaping Filters
213
+
214
+ For safely passing values to shell commands:
215
+
216
+ ```liquid
217
+ # Single-quote escaping (recommended, POSIX-compliant)
218
+ {{ value | shell_escape }} # "hello'world" becomes "'hello'\''world'"
219
+ {{ value | escape_shell }} # Alias for shell_escape
220
+
221
+ # Double-quote escaping (less safe, use when needed)
222
+ {{ value | shell_escape_double }} # Escapes $, `, \, ", and !
223
+ ```
224
+
225
+ ### Utility Filters
226
+
227
+ ```liquid
228
+ # Safe nested access using dot-path
229
+ {{ obj | get: 'a.b.c' }} # Access nested property safely
230
+
231
+ # Check if value is non-empty
232
+ {% if items | not_empty %} # True for non-empty arrays/strings/objects
233
+ Has items
234
+ {% endif %}
235
+
236
+ # Pick first non-empty value
237
+ {{ a | coalesce: b, c }} # Returns first non-empty value
238
+
239
+ # Expression-based array filtering (Shopify-style)
240
+ {{ items | where_exp: 'i', 'i.is_valid == true' }}
241
+
242
+ # String manipulation
243
+ {{ value | unescape_newlines }} # Convert "\n" to actual newlines
244
+ ```
245
+
246
+ ### Label Sanitization Filters
247
+
248
+ For safely formatting labels (only allows `[A-Za-z0-9:/\- ]`):
249
+
250
+ ```liquid
251
+ {{ label | safe_label }} # Sanitize a single label
252
+ {{ labels | safe_label_list }} # Sanitize an array of labels
253
+ ```
254
+
255
+ ### Memory Store Filters
256
+
257
+ Access the persistent memory store from templates:
258
+
259
+ ```liquid
260
+ # Get a value from memory
261
+ {{ "my-key" | memory_get }}
262
+ {{ "my-key" | memory_get: "namespace" }}
263
+
264
+ # Check if a key exists
265
+ {% if "my-key" | memory_has %}
266
+ Key exists in memory
267
+ {% endif %}
268
+
269
+ # List all keys in a namespace
270
+ {{ "namespace" | memory_list }}
271
+ ```
272
+
201
273
  ### Chat History Helper
202
274
 
203
275
  The `chat_history` filter turns one or more check histories into a linear, timestamp‑sorted chat transcript. This is especially useful for human‑input + AI chat flows (Slack, CLI, etc.).
@@ -25,14 +25,16 @@ The previous behavior de‑duplicated a step when re‑routed in the same event,
25
25
  - Skip static `on_success.goto` chains when the target produced fatal issues (including `fail_if`).
26
26
  - For `origin='on_fail'`, schedule only direct dependents of the failed target; skip dependents when any direct dep has fatal issues.
27
27
 
28
- 4) One‑shot opt‑in
29
- - `tags: [one_shot]` prevents a terminal step (e.g., `finish`) from running more than once per grouped run.
28
+ 4) One‑shot opt‑in (NOT YET IMPLEMENTED)
29
+ - `tags: [one_shot]` would prevent a terminal step (e.g., `finish`) from running more than once per grouped run.
30
+ - Status: Planned but not yet implemented in codebase.
30
31
 
31
32
  5) Test‑visible history
32
33
  - `executeChecks` now attaches `reviewSummary.history` with a safe snapshot of per‑step outputs history for deterministic testing (no I/O).
33
34
 
34
35
  6) Task‑refinement agent (manual‑only)
35
36
  - `defaults/task-refinement.yaml` uses `ask` → `refine` loop with `fail_if` and `on_fail/on_success` only; no `repeatable`, no `goto_js`, no `schedule`.
37
+ - The `finish` step uses an `if` guard to prevent re-execution (workaround for unimplemented `one_shot` tag).
36
38
  - Embedded tests: one‑pass and multi‑turn pass locally.
37
39
 
38
40
  ## Removed