@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,11 +1,17 @@
1
1
  # Failure Routing (Retry/Goto/Remediate) — RFC
2
2
 
3
- Status: Draft
3
+ Status: **Implemented** (fully landed in engine, Phase 5 fanout/reduce included)
4
4
 
5
- Last updated: 2025-10-03
5
+ Last updated: 2026-01-28
6
6
 
7
7
  Owner: Visor team
8
8
 
9
+ > **Note**: This RFC has been fully implemented. For user documentation, see
10
+ > [failure-routing.md](./failure-routing.md). The implementation includes all
11
+ > core features: `on_fail`, `on_success`, `on_finish`, retry with backoff,
12
+ > `goto`/`goto_js`, `run`/`run_js`, `goto_event`, fanout/reduce semantics, loop
13
+ > budgets, and forEach scope isolation.
14
+
9
15
  ## Objectives
10
16
 
11
17
  - Enable a workflow step to handle failure by retrying, jumping back to a prior step, or running a remediation step, then continuing.
@@ -20,7 +26,9 @@ Owner: Visor team
20
26
 
21
27
  ## Config Sketch (MVP)
22
28
 
23
- Proposed additions use Visors existing 2.0 style (type/exec/depends_on). New keys are `on_fail`, `on_success`, and optional top‑level `routing` for defaults.
29
+ Proposed additions use Visor's existing 2.0 style (type/exec/depends_on). New keys are `on_fail`, `on_success`, `on_finish`, and optional top‑level `routing` for defaults.
30
+
31
+ > **Implementation note**: The implementation also added `transitions` (declarative rule-based routing) and `goto_event` (event override for goto targets). See [failure-routing.md](./failure-routing.md) for full documentation.
24
32
 
25
33
  ```yaml
26
34
  version: "2.0"
@@ -157,9 +165,10 @@ steps:
157
165
 
158
166
  ## CLI and UX
159
167
 
160
- - Flags: `--on-fail-max-loops`, `--retry-max`, `--no-failure-routing` (to disable feature globally).
168
+ - **Future Flags** (not yet implemented): `--on-fail-max-loops`, `--retry-max`, `--no-failure-routing` (to disable feature globally).
161
169
  - Run summary shows failure routes taken with timestamps and attempt counts.
162
170
  - Debug: when `--debug` is set, include evaluated `*_js` results (with sensitive data redacted), sandbox timing, retry/backoff decisions, goto/run transitions, and per-scope loop counters.
171
+ - Telemetry: routing decisions are traced via OTel events (`visor.routing`) with attributes like `trigger`, `action`, `target`, `source`, `scope`, and `goto_event`.
163
172
 
164
173
  ## Tests and Demo
165
174
 
@@ -168,14 +177,24 @@ steps:
168
177
 
169
178
  ## Acceptance Criteria
170
179
 
171
- - Goto: Given a failing step with `goto: setup-env`, engine jumps to `setup-env`, proceeds, and re-runs the failed step.
172
- - Remediation: Given `run: [lint-fix]`, if remediation succeeds, the failed step re-runs once; if remediation fails, the run stops with a clear message.
173
- - Retry: Per-step retries respect backoff and caps; global `max_loops` prevents infinite ping-pong.
174
- - Compatibility: Configs without `on_fail` behave exactly as today.
175
- - Observability: Logs show ordered trace of retries and jumps; exit codes reflect final outcome.
176
- - Dynamic routing: `goto_js` and `run_js` work with pure, time-limited evaluation; precedence and merging behave as specified.
177
- - forEach: Each item runs with isolated counters; `*_js` receives `foreach` context and cannot jump across scopes.
178
- - on_success goto: After a step succeeds, `goto` (ancestor-only) can jump back to a prior step; with `max_loops` enforcement the run either converges or fails with a clear trace.
180
+ All original acceptance criteria have been met:
181
+
182
+ - [x] **Goto**: Given a failing step with `goto: setup-env`, engine jumps to `setup-env`, proceeds, and re-runs the failed step.
183
+ - [x] **Remediation**: Given `run: [lint-fix]`, if remediation succeeds, the failed step re-runs once; if remediation fails, the run stops with a clear message.
184
+ - [x] **Retry**: Per-step retries respect backoff and caps; global `max_loops` prevents infinite ping-pong.
185
+ - [x] **Compatibility**: Configs without `on_fail` behave exactly as today.
186
+ - [x] **Observability**: Logs show ordered trace of retries and jumps; exit codes reflect final outcome.
187
+ - [x] **Dynamic routing**: `goto_js` and `run_js` work with pure, time-limited evaluation; precedence and merging behave as specified.
188
+ - [x] **forEach**: Each item runs with isolated counters; `*_js` receives `foreach` context and cannot jump across scopes.
189
+ - [x] **on_success goto**: After a step succeeds, `goto` (ancestor-only) can jump back to a prior step; with `max_loops` enforcement the run either converges or fails with a clear trace.
190
+
191
+ Additional features implemented beyond the original RFC:
192
+
193
+ - [x] **on_finish hook**: Runs after all forEach iterations and dependent checks complete — ideal for aggregation.
194
+ - [x] **goto_event**: Override the event trigger when performing a goto (e.g., simulate `pr_updated`).
195
+ - [x] **Declarative transitions**: Rule-based routing via `transitions: [{ when, to, goto_event }]` in on_fail/on_success/on_finish.
196
+ - [x] **Fanout/reduce**: Control whether routing targets run per-item (`fanout: map`) or as a single aggregation (`fanout: reduce`).
197
+ - [x] **Criticality-aware retry suppression**: High-criticality steps skip retries for logical failures (fail_if/guarantee).
179
198
 
180
199
  ## Open Questions
181
200
 
@@ -184,10 +203,16 @@ steps:
184
203
  - Any preferred global default (e.g., retry once everywhere with linear 2s backoff)?
185
204
  - Should we allow opt-in cross-scope targets via explicit qualifiers (e.g., `parent:setup-env`), guarded by additional loop caps?
186
205
 
187
- ## Next Steps
206
+ ## Next Steps (Completed)
207
+
208
+ All primary implementation work is done:
209
+
210
+ 1. ~~Audit current workflow engine & failure handling.~~ Done.
211
+ 2. ~~Finalize config keys and schema validation messages.~~ Done — see `src/types/config.ts`.
212
+ 3. ~~Implement engine changes with loop safeguards.~~ Done — see `src/state-machine/states/routing.ts`.
213
+ 4. ~~Add tests and demos.~~ Done — see `tests/integration/routing-*.test.ts` and `examples/routing-*.yaml`.
214
+ 5. ~~Extend docs and workshop slides.~~ Done — see [failure-routing.md](./failure-routing.md).
188
215
 
189
- 1. Audit current workflow engine & failure handling.
190
- 2. Finalize config keys and schema validation messages.
191
- 3. Implement engine changes with loop safeguards.
192
- 4. Add tests and the `lab-05-retry.yaml` demo.
193
- 5. Extend docs and workshop slides.
216
+ Remaining follow-ups:
217
+ - CLI flags (`--on-fail-max-loops`, `--retry-max`, `--no-failure-routing`) are planned but not yet implemented.
218
+ - Consider adding labeled checkpoints and opt-in cross-scope targets (see Open Questions).
@@ -77,7 +77,8 @@ steps:
77
77
  on_success:
78
78
  run: [notify]
79
79
  goto_js: |
80
- return attempt === 1 ? 'unit' : null; # only once
80
+ // Jump back only once using history length as proxy for attempt count
81
+ return outputs.history['build'].length === 1 ? 'unit' : null;
81
82
  notify: { type: command, exec: "echo notify" }
82
83
  ```
83
84
 
@@ -117,10 +118,45 @@ Per-step actions:
117
118
  - `retry`: `{ max, backoff: { mode: fixed|exponential, delay_ms } }`
118
119
  - `run`: `[step-id, …]`
119
120
  - `goto`: `step-id` (ancestor-only)
121
+ - `goto_event`: event to simulate during goto (e.g., `pr_updated`)
120
122
  - `run_js`: JS returning `string[]`
121
123
  - `goto_js`: JS returning `string | null`
124
+ - `transitions`: declarative transition rules (see below)
122
125
  - `on_success`:
123
- - `run`, `goto`, `run_js`, `goto_js` (same types and constraints as above)
126
+ - `run`, `goto`, `goto_event`, `run_js`, `goto_js`, `transitions` (same types and constraints as above)
127
+ - `on_finish` (forEach checks only):
128
+ - `run`, `goto`, `goto_event`, `run_js`, `goto_js`, `transitions` (same types and constraints as above)
129
+
130
+ ### Declarative Transitions
131
+
132
+ The `transitions` field provides a declarative alternative to `goto_js`. It is an array of rules evaluated in order; the first matching rule wins:
133
+
134
+ ```yaml
135
+ steps:
136
+ validate:
137
+ type: ai
138
+ on_success:
139
+ transitions:
140
+ - when: "outputs['validate'].score >= 90"
141
+ to: publish
142
+ - when: "outputs['validate'].score >= 70"
143
+ to: review
144
+ - when: "true" # default fallback
145
+ to: reject
146
+ ```
147
+
148
+ Each rule has:
149
+ - `when`: JavaScript expression evaluated in the same sandbox as `goto_js`
150
+ - `to`: target step ID, or `null` to explicitly skip goto
151
+ - `goto_event`: optional event override (same as `goto_event` above)
152
+
153
+ Helper functions available in `when` expressions:
154
+ - `any(arr, pred)` - returns true if any element matches predicate
155
+ - `all(arr, pred)` - returns true if all elements match predicate
156
+ - `none(arr, pred)` - returns true if no element matches predicate
157
+ - `count(arr, pred)` - returns count of elements matching predicate
158
+
159
+ When `transitions` is present, it takes precedence over `goto_js` and `goto`.
124
160
 
125
161
  ## Semantics
126
162
 
@@ -128,7 +164,7 @@ Per-step actions:
128
164
  - Run: on failure (or success), run listed steps first; if successful, the failed step is re-attempted once (failure path).
129
165
  - Goto (ancestor-only): jump back to a previously executed dependency, then continue forward. On success, Visor re-runs the current step once after the jump.
130
166
  - Loop safety: `routing.max_loops` counts all routing transitions (runs, gotos, retries). Exceeding it aborts the current scope with a clear error. For a hard cap on repeated executions of the same step, see [Execution Limits](./limits.md).
131
- - forEach: each item is isolated with its own loop/attempt counters; `*_js` receives `{ foreach: { index, total, parent } }`.
167
+ - forEach: each item is isolated with its own loop/attempt counters; `*_js` in on_finish context receives forEach metadata (see [Available Context in on_finish](#available-context-in-on_finish)).
132
168
 
133
169
  ### Fan‑out vs. Reduce (Phase 5)
134
170
 
@@ -214,7 +250,8 @@ steps:
214
250
  on: [pr_opened, pr_updated]
215
251
  on_success:
216
252
  goto_js: |
217
- return attempt === 1 ? 'overview' : null
253
+ // Jump back only once using history length as proxy for attempt count
254
+ return outputs.history['quality'].length === 1 ? 'overview' : null
218
255
  goto_event: pr_updated
219
256
  ```
220
257
 
@@ -225,20 +262,26 @@ When to use goto_event vs. full re-run:
225
262
  ## Dynamic JS (safe, sync only)
226
263
 
227
264
  - `goto_js` / `run_js` are evaluated in a sandbox with:
228
- - Read-only context: `{ step, attempt, loop, error, foreach, outputs, pr, files, env }`
265
+ - Read-only context: `{ step, outputs, outputs_history, outputs_raw, output, memory, event, forEach }`
229
266
  - `outputs` contains current values, `outputs.history` contains arrays of all previous values (see [Output History](./output-history.md))
267
+ - `outputs_raw` provides aggregate/parent values (e.g., full array from forEach parent)
268
+ - `memory` provides read/write access to the memory store (get, set, has, getAll, increment, clear)
269
+ - `log()` function available for debugging (outputs with `Debug:` prefix)
230
270
  - Pure sync execution; no IO, no async, no timers, no require/process.
231
271
  - Time and size limits (short wall time; small code/output caps) — evaluation failures fall back to static routing.
232
272
 
273
+ Note: The `on_finish` context is richer and additionally includes `attempt`, `loop`, `pr`, `files`, and `env`.
274
+
233
275
  Return types:
234
276
  - `goto_js`: a `string` (step id) or `null`/`undefined` to skip.
235
277
  - `run_js`: a `string[]` (may be empty). Duplicates are removed preserving order.
236
278
 
237
279
  ## Guardrails & Tips
238
280
 
239
- - Keep `max_loops` small (510). Add retries sparingly and prefer remediation over blind loops.
281
+ - Keep `max_loops` small (5-10). Add retries sparingly and prefer remediation over blind loops.
240
282
  - Restrict `goto` to ancestors to preserve dependency semantics and avoid hard-to-reason paths.
241
- - For expensive remediations, put them behind `run_js` conditions keyed to `error.message`/`stderr`.
283
+ - For expensive remediations, put them behind `run_js` conditions keyed to `output` or `outputs.history` values.
284
+ - Use `outputs.history['check-name'].length` as a proxy for attempt count in `goto_js`/`run_js`.
242
285
  - In CI, you can override defaults with CLI flags (future): `--on-fail-max-loops`, `--retry-max`.
243
286
 
244
287
  ## on_finish Hook (forEach Aggregation & Routing)
@@ -289,30 +332,44 @@ The `on_finish` hooks have access to the complete execution context:
289
332
  {
290
333
  step: { id: 'extract-facts', tags: [...], group: '...' },
291
334
  attempt: 1, // Current attempt number for this check
292
- loop: 2, // Current loop number in routing
335
+ loop: 0, // Current loop number in routing
293
336
  outputs: {
294
- 'extract-facts': [...], // Array of forEach items
295
- 'validate-fact': [...], // Array of ALL dependent results
337
+ 'extract-facts': [...], // Array of forEach items
338
+ 'validate-fact': [...], // Array of ALL dependent results
339
+ history: { ... } // Alias for outputs_history
296
340
  },
297
- outputs.history: {
341
+ outputs_history: {
298
342
  'extract-facts': [[...], ...], // Cross-loop history
299
343
  'validate-fact': [[...], ...], // All results from all iterations
300
344
  },
301
- // Alias (also available):
302
- outputs_history: {
303
- 'extract-facts': [[...], ...],
304
- 'validate-fact': [[...], ...],
345
+ outputs_raw: {
346
+ 'extract-facts': [...], // Aggregate/parent values
347
+ 'validate-fact': [...],
305
348
  },
306
349
  forEach: {
307
- total: 3, // Total number of items
308
- successful: 3, // Number of successful iterations
309
- failed: 0, // Number of failed iterations
310
- items: [...] // The forEach items array
350
+ items: 3, // Number of forEach items
351
+ last_wave_size: 3, // Items in the last wave (when forEach parent)
352
+ last_items: [...], // The forEach items array (when forEach parent)
353
+ is_parent: true // Indicates this check is a forEach parent
354
+ },
355
+ memory: { // Memory access functions
356
+ get: (key, ns?) => ...,
357
+ set: (key, value, ns?) => ...,
358
+ has: (key, ns?) => ...,
359
+ getAll: (ns?) => ...,
360
+ increment: (key, amount?, ns?) => ...,
361
+ clear: (ns?) => ...
362
+ },
363
+ pr: { // PR metadata
364
+ number: 123,
365
+ title: '...',
366
+ author: '...',
367
+ branch: '...',
368
+ base: '...'
311
369
  },
312
- memory, // Memory access functions
313
- pr, // PR metadata
314
- files, // Changed files
315
- env // Environment variables
370
+ files: [...], // Changed files
371
+ env: { ... }, // Environment variables (filtered for safety)
372
+ event: { name: '...' } // Event that triggered execution
316
373
  }
317
374
  ```
318
375