@probelabs/visor 0.1.107 → 0.1.112

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 (235) hide show
  1. package/README.md +6 -0
  2. package/defaults/task-refinement.yaml +7 -3
  3. package/defaults/visor.tests.yaml +13 -2
  4. package/defaults/visor.yaml +1 -0
  5. package/dist/663.index.js +3 -2
  6. package/dist/80.index.js +3 -2
  7. package/dist/ai-review-service.d.ts +13 -9
  8. package/dist/ai-review-service.d.ts.map +1 -1
  9. package/dist/cli-main.d.ts.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/debug-visualizer/ws-server.d.ts +7 -1
  13. package/dist/debug-visualizer/ws-server.d.ts.map +1 -1
  14. package/dist/defaults/task-refinement.yaml +7 -3
  15. package/dist/defaults/visor.tests.yaml +13 -2
  16. package/dist/defaults/visor.yaml +1 -0
  17. package/dist/docs/advanced-ai.md +60 -1
  18. package/dist/docs/ai-configuration.md +67 -0
  19. package/dist/docs/ai-custom-tools-usage.md +261 -0
  20. package/dist/docs/ai-custom-tools.md +392 -0
  21. package/dist/docs/bot-transports-rfc.md +23 -0
  22. package/dist/docs/configuration.md +21 -0
  23. package/dist/docs/engine-pause-resume-rfc.md +192 -0
  24. package/dist/docs/lifecycle-hooks.md +253 -0
  25. package/dist/docs/liquid-templates.md +143 -0
  26. package/dist/docs/providers/git-checkout.md +589 -0
  27. package/dist/docs/recipes.md +458 -5
  28. package/dist/docs/rfc/git-checkout-step.md +601 -0
  29. package/dist/docs/rfc/on_init-hook.md +1294 -0
  30. package/dist/docs/rfc/workspace-isolation.md +216 -0
  31. package/dist/docs/router-patterns.md +339 -0
  32. package/dist/event-bus/types.d.ts +14 -0
  33. package/dist/event-bus/types.d.ts.map +1 -1
  34. package/dist/examples/ai-custom-tools-example.yaml +206 -0
  35. package/dist/examples/ai-custom-tools-simple.yaml +76 -0
  36. package/dist/examples/git-checkout-basic.yaml +32 -0
  37. package/dist/examples/git-checkout-compare.yaml +59 -0
  38. package/dist/examples/git-checkout-cross-repo.yaml +76 -0
  39. package/dist/examples/on-init-import-demo.yaml +179 -0
  40. package/dist/examples/reusable-tools.yaml +92 -0
  41. package/dist/examples/reusable-workflows.yaml +88 -0
  42. package/dist/examples/session-reuse-self.yaml +81 -0
  43. package/dist/examples/slack-simple-chat.yaml +775 -0
  44. package/dist/failure-condition-evaluator.d.ts +2 -0
  45. package/dist/failure-condition-evaluator.d.ts.map +1 -1
  46. package/dist/frontends/github-frontend.d.ts +20 -0
  47. package/dist/frontends/github-frontend.d.ts.map +1 -1
  48. package/dist/frontends/host.d.ts +4 -0
  49. package/dist/frontends/host.d.ts.map +1 -1
  50. package/dist/frontends/slack-frontend.d.ts +58 -0
  51. package/dist/frontends/slack-frontend.d.ts.map +1 -0
  52. package/dist/generated/config-schema.d.ts +409 -41
  53. package/dist/generated/config-schema.d.ts.map +1 -1
  54. package/dist/generated/config-schema.json +436 -47
  55. package/dist/github-comments.d.ts +2 -0
  56. package/dist/github-comments.d.ts.map +1 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +83587 -56085
  59. package/dist/liquid-extensions.d.ts.map +1 -1
  60. package/dist/logger.d.ts +1 -0
  61. package/dist/logger.d.ts.map +1 -1
  62. package/dist/output/traces/{run-2025-11-21T11-50-46-505Z.ndjson → run-2026-01-21T05-37-24-446Z.ndjson} +91 -91
  63. package/dist/output/traces/run-2026-01-21T05-38-18-580Z.ndjson +1067 -0
  64. package/dist/output-formatters.d.ts.map +1 -1
  65. package/dist/providers/ai-check-provider.d.ts +12 -0
  66. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  67. package/dist/providers/check-provider-registry.d.ts.map +1 -1
  68. package/dist/providers/check-provider.interface.d.ts +9 -0
  69. package/dist/providers/check-provider.interface.d.ts.map +1 -1
  70. package/dist/providers/command-check-provider.d.ts.map +1 -1
  71. package/dist/providers/custom-tool-executor.d.ts.map +1 -1
  72. package/dist/providers/git-checkout-provider.d.ts +25 -0
  73. package/dist/providers/git-checkout-provider.d.ts.map +1 -0
  74. package/dist/providers/http-client-provider.d.ts +3 -0
  75. package/dist/providers/http-client-provider.d.ts.map +1 -1
  76. package/dist/providers/human-input-check-provider.d.ts +2 -0
  77. package/dist/providers/human-input-check-provider.d.ts.map +1 -1
  78. package/dist/providers/log-check-provider.d.ts.map +1 -1
  79. package/dist/providers/mcp-check-provider.d.ts +1 -1
  80. package/dist/providers/mcp-check-provider.d.ts.map +1 -1
  81. package/dist/providers/mcp-custom-sse-server.d.ts +66 -0
  82. package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -0
  83. package/dist/providers/memory-check-provider.d.ts.map +1 -1
  84. package/dist/providers/script-check-provider.d.ts.map +1 -1
  85. package/dist/providers/workflow-check-provider.d.ts.map +1 -1
  86. package/dist/reviewer.d.ts.map +1 -1
  87. package/dist/sdk/check-provider-registry-534KL5HT.mjs +27 -0
  88. package/dist/sdk/chunk-23L3QRYX.mjs +16872 -0
  89. package/dist/sdk/chunk-23L3QRYX.mjs.map +1 -0
  90. package/dist/sdk/{chunk-OOZITMRU.mjs → chunk-3OMWVM6J.mjs} +11 -1
  91. package/dist/sdk/{chunk-OOZITMRU.mjs.map → chunk-3OMWVM6J.mjs.map} +1 -1
  92. package/dist/sdk/{chunk-37ZSCMFC.mjs → chunk-7UK3NIIT.mjs} +2 -2
  93. package/dist/sdk/{chunk-VMPLF6FT.mjs → chunk-AGIZJ4UZ.mjs} +50 -4
  94. package/dist/sdk/chunk-AGIZJ4UZ.mjs.map +1 -0
  95. package/dist/sdk/{chunk-IEO6CFLG.mjs → chunk-AIVFBIS4.mjs} +161 -5
  96. package/dist/sdk/chunk-AIVFBIS4.mjs.map +1 -0
  97. package/dist/sdk/chunk-AK6BVWIT.mjs +426 -0
  98. package/dist/sdk/chunk-AK6BVWIT.mjs.map +1 -0
  99. package/dist/sdk/chunk-AUT26LHW.mjs +139 -0
  100. package/dist/sdk/chunk-AUT26LHW.mjs.map +1 -0
  101. package/dist/sdk/chunk-BOVFH3LI.mjs +232 -0
  102. package/dist/sdk/chunk-BOVFH3LI.mjs.map +1 -0
  103. package/dist/sdk/chunk-HTOKWMPO.mjs +157 -0
  104. package/dist/sdk/chunk-HTOKWMPO.mjs.map +1 -0
  105. package/dist/sdk/{chunk-6Y4YTKCF.mjs → chunk-NAW3DB3I.mjs} +2 -2
  106. package/dist/sdk/{chunk-OWUVOILT.mjs → chunk-QR7MOMJH.mjs} +4 -3
  107. package/dist/sdk/{chunk-OWUVOILT.mjs.map → chunk-QR7MOMJH.mjs.map} +1 -1
  108. package/dist/sdk/{chunk-PTL3K3PN.mjs → chunk-QY2XYPEV.mjs} +488 -60
  109. package/dist/sdk/chunk-QY2XYPEV.mjs.map +1 -0
  110. package/dist/sdk/{chunk-OZJ263FM.mjs → chunk-SIWNBRTK.mjs} +29 -215
  111. package/dist/sdk/chunk-SIWNBRTK.mjs.map +1 -0
  112. package/dist/sdk/command-executor-TYUV6HUS.mjs +14 -0
  113. package/dist/sdk/{config-M4ZNO6NU.mjs → config-YNC2EOOT.mjs} +5 -3
  114. package/dist/sdk/{failure-condition-evaluator-NBO5YRXW.mjs → failure-condition-evaluator-YGTF2GHG.mjs} +6 -5
  115. package/dist/sdk/{github-frontend-4AWRJT7D.mjs → github-frontend-SIAEOCON.mjs} +190 -12
  116. package/dist/sdk/github-frontend-SIAEOCON.mjs.map +1 -0
  117. package/dist/sdk/{host-7GBC3S7L.mjs → host-DXUYTNMU.mjs} +5 -2
  118. package/dist/sdk/host-DXUYTNMU.mjs.map +1 -0
  119. package/dist/sdk/{liquid-extensions-C7EG3YKH.mjs → liquid-extensions-PKWCKK7E.mjs} +5 -4
  120. package/dist/sdk/memory-store-XGBB7LX7.mjs +12 -0
  121. package/dist/sdk/prompt-state-YRJY6QAL.mjs +16 -0
  122. package/dist/sdk/{renderer-schema-6RF26VUS.mjs → renderer-schema-LPKN5UJS.mjs} +3 -2
  123. package/dist/sdk/{renderer-schema-6RF26VUS.mjs.map → renderer-schema-LPKN5UJS.mjs.map} +1 -1
  124. package/dist/sdk/{routing-RP56JTV2.mjs → routing-6N45MJ4F.mjs} +7 -6
  125. package/dist/sdk/sdk.d.mts +219 -5
  126. package/dist/sdk/sdk.d.ts +219 -5
  127. package/dist/sdk/sdk.js +21329 -14908
  128. package/dist/sdk/sdk.js.map +1 -1
  129. package/dist/sdk/sdk.mjs +407 -12874
  130. package/dist/sdk/sdk.mjs.map +1 -1
  131. package/dist/sdk/{session-registry-N5FFYFTM.mjs → session-registry-4E6YRQ77.mjs} +2 -2
  132. package/dist/sdk/session-registry-4E6YRQ77.mjs.map +1 -0
  133. package/dist/sdk/slack-frontend-BVKW3GD5.mjs +735 -0
  134. package/dist/sdk/slack-frontend-BVKW3GD5.mjs.map +1 -0
  135. package/dist/sdk/{tracer-init-WP4X46IF.mjs → tracer-init-GSLPPLCD.mjs} +2 -2
  136. package/dist/sdk/tracer-init-GSLPPLCD.mjs.map +1 -0
  137. package/dist/sdk/workflow-registry-R6KSACFR.mjs +12 -0
  138. package/dist/sdk/workflow-registry-R6KSACFR.mjs.map +1 -0
  139. package/dist/slack/adapter.d.ts +36 -0
  140. package/dist/slack/adapter.d.ts.map +1 -0
  141. package/dist/slack/cache-prewarmer.d.ts +31 -0
  142. package/dist/slack/cache-prewarmer.d.ts.map +1 -0
  143. package/dist/slack/client.d.ts +77 -0
  144. package/dist/slack/client.d.ts.map +1 -0
  145. package/dist/slack/markdown.d.ts +45 -0
  146. package/dist/slack/markdown.d.ts.map +1 -0
  147. package/dist/slack/prompt-state.d.ts +33 -0
  148. package/dist/slack/prompt-state.d.ts.map +1 -0
  149. package/dist/slack/rate-limiter.d.ts +56 -0
  150. package/dist/slack/rate-limiter.d.ts.map +1 -0
  151. package/dist/slack/signature.d.ts +2 -0
  152. package/dist/slack/signature.d.ts.map +1 -0
  153. package/dist/slack/socket-runner.d.ts +42 -0
  154. package/dist/slack/socket-runner.d.ts.map +1 -0
  155. package/dist/slack/thread-cache.d.ts +51 -0
  156. package/dist/slack/thread-cache.d.ts.map +1 -0
  157. package/dist/state-machine/context/build-engine-context.d.ts +8 -0
  158. package/dist/state-machine/context/build-engine-context.d.ts.map +1 -1
  159. package/dist/state-machine/dispatch/execution-invoker.d.ts.map +1 -1
  160. package/dist/state-machine/dispatch/foreach-processor.d.ts.map +1 -1
  161. package/dist/state-machine/dispatch/on-init-handlers.d.ts +43 -0
  162. package/dist/state-machine/dispatch/on-init-handlers.d.ts.map +1 -0
  163. package/dist/state-machine/dispatch/stats-manager.d.ts.map +1 -1
  164. package/dist/state-machine/dispatch/template-renderer.d.ts.map +1 -1
  165. package/dist/state-machine/runner.d.ts +6 -0
  166. package/dist/state-machine/runner.d.ts.map +1 -1
  167. package/dist/state-machine/states/level-dispatch.d.ts.map +1 -1
  168. package/dist/state-machine/states/plan-ready.d.ts.map +1 -1
  169. package/dist/state-machine/states/routing.d.ts.map +1 -1
  170. package/dist/state-machine/states/wave-planning.d.ts.map +1 -1
  171. package/dist/state-machine/workflow-projection.d.ts.map +1 -1
  172. package/dist/state-machine-execution-engine.d.ts +21 -9
  173. package/dist/state-machine-execution-engine.d.ts.map +1 -1
  174. package/dist/telemetry/state-capture.d.ts +5 -0
  175. package/dist/telemetry/state-capture.d.ts.map +1 -1
  176. package/dist/test-runner/core/flow-stage.d.ts.map +1 -1
  177. package/dist/test-runner/core/test-execution-wrapper.d.ts.map +1 -1
  178. package/dist/test-runner/evaluators.d.ts +37 -4
  179. package/dist/test-runner/evaluators.d.ts.map +1 -1
  180. package/dist/test-runner/index.d.ts +7 -0
  181. package/dist/test-runner/index.d.ts.map +1 -1
  182. package/dist/test-runner/recorders/slack-recorder.d.ts +17 -0
  183. package/dist/test-runner/recorders/slack-recorder.d.ts.map +1 -0
  184. package/dist/test-runner/validator.d.ts.map +1 -1
  185. package/dist/traces/{run-2025-11-21T11-50-46-505Z.ndjson → run-2026-01-21T05-37-24-446Z.ndjson} +91 -91
  186. package/dist/traces/run-2026-01-21T05-38-18-580Z.ndjson +1067 -0
  187. package/dist/types/bot.d.ts +109 -0
  188. package/dist/types/bot.d.ts.map +1 -0
  189. package/dist/types/cli.d.ts +4 -0
  190. package/dist/types/cli.d.ts.map +1 -1
  191. package/dist/types/config.d.ts +182 -5
  192. package/dist/types/config.d.ts.map +1 -1
  193. package/dist/types/engine.d.ts +5 -0
  194. package/dist/types/engine.d.ts.map +1 -1
  195. package/dist/types/git-checkout.d.ts +76 -0
  196. package/dist/types/git-checkout.d.ts.map +1 -0
  197. package/dist/utils/json-text-extractor.d.ts +17 -0
  198. package/dist/utils/json-text-extractor.d.ts.map +1 -0
  199. package/dist/utils/sandbox.d.ts +10 -0
  200. package/dist/utils/sandbox.d.ts.map +1 -1
  201. package/dist/utils/template-context.d.ts +1 -0
  202. package/dist/utils/template-context.d.ts.map +1 -1
  203. package/dist/utils/tracer-init.d.ts.map +1 -1
  204. package/dist/utils/workspace-manager.d.ts +118 -0
  205. package/dist/utils/workspace-manager.d.ts.map +1 -0
  206. package/dist/utils/worktree-cleanup.d.ts +33 -0
  207. package/dist/utils/worktree-cleanup.d.ts.map +1 -0
  208. package/dist/utils/worktree-manager.d.ts +153 -0
  209. package/dist/utils/worktree-manager.d.ts.map +1 -0
  210. package/dist/webhook-server.d.ts.map +1 -1
  211. package/dist/workflow-executor.d.ts.map +1 -1
  212. package/dist/workflow-registry.d.ts.map +1 -1
  213. package/package.json +5 -4
  214. package/dist/output/traces/run-2025-11-21T11-51-33-674Z.ndjson +0 -839
  215. package/dist/sdk/chunk-IEO6CFLG.mjs.map +0 -1
  216. package/dist/sdk/chunk-JEHPDJIF.mjs +0 -223
  217. package/dist/sdk/chunk-JEHPDJIF.mjs.map +0 -1
  218. package/dist/sdk/chunk-OZJ263FM.mjs.map +0 -1
  219. package/dist/sdk/chunk-PTL3K3PN.mjs.map +0 -1
  220. package/dist/sdk/chunk-VMPLF6FT.mjs.map +0 -1
  221. package/dist/sdk/github-frontend-4AWRJT7D.mjs.map +0 -1
  222. package/dist/sdk/host-7GBC3S7L.mjs.map +0 -1
  223. package/dist/sdk/memory-store-GJACZC2A.mjs +0 -11
  224. package/dist/sdk/workflow-registry-2YIIXQCK.mjs +0 -11
  225. package/dist/traces/run-2025-11-21T11-51-33-674Z.ndjson +0 -839
  226. /package/dist/sdk/{config-M4ZNO6NU.mjs.map → check-provider-registry-534KL5HT.mjs.map} +0 -0
  227. /package/dist/sdk/{chunk-37ZSCMFC.mjs.map → chunk-7UK3NIIT.mjs.map} +0 -0
  228. /package/dist/sdk/{chunk-6Y4YTKCF.mjs.map → chunk-NAW3DB3I.mjs.map} +0 -0
  229. /package/dist/sdk/{failure-condition-evaluator-NBO5YRXW.mjs.map → command-executor-TYUV6HUS.mjs.map} +0 -0
  230. /package/dist/sdk/{liquid-extensions-C7EG3YKH.mjs.map → config-YNC2EOOT.mjs.map} +0 -0
  231. /package/dist/sdk/{memory-store-GJACZC2A.mjs.map → failure-condition-evaluator-YGTF2GHG.mjs.map} +0 -0
  232. /package/dist/sdk/{routing-RP56JTV2.mjs.map → liquid-extensions-PKWCKK7E.mjs.map} +0 -0
  233. /package/dist/sdk/{session-registry-N5FFYFTM.mjs.map → memory-store-XGBB7LX7.mjs.map} +0 -0
  234. /package/dist/sdk/{tracer-init-WP4X46IF.mjs.map → prompt-state-YRJY6QAL.mjs.map} +0 -0
  235. /package/dist/sdk/{workflow-registry-2YIIXQCK.mjs.map → routing-6N45MJ4F.mjs.map} +0 -0
@@ -0,0 +1,1294 @@
1
+ # RFC: `on_init` Lifecycle Hook for Context Preprocessing
2
+
3
+ **Status:** Proposal
4
+ **Author:** Visor Team
5
+ **Created:** 2024-01-XX
6
+ **Updated:** 2024-01-XX
7
+
8
+ ---
9
+
10
+ ## Abstract
11
+
12
+ This RFC proposes adding an `on_init` lifecycle hook to enable automatic context enrichment before step execution. This solves the link preprocessing problem (JIRA, Linear, etc.) and provides a general-purpose mechanism for setup, data fetching, and context preparation.
13
+
14
+ ---
15
+
16
+ ## Table of Contents
17
+
18
+ 1. [Motivation](#motivation)
19
+ 2. [Proposed Solution](#proposed-solution)
20
+ 3. [Detailed Design](#detailed-design)
21
+ 4. [Type Definitions](#type-definitions)
22
+ 5. [Syntax Specifications](#syntax-specifications)
23
+ 6. [Execution Semantics](#execution-semantics)
24
+ 7. [Implementation Plan](#implementation-plan)
25
+ 8. [Examples](#examples)
26
+ 9. [Alternatives Considered](#alternatives-considered)
27
+ 10. [Migration Path](#migration-path)
28
+ 11. [Open Questions](#open-questions)
29
+
30
+ ---
31
+
32
+ ## 1. Motivation
33
+
34
+ ### 1.1 Problem Statement
35
+
36
+ **Use Case:** Automatically enrich AI prompts with external context (JIRA tickets, Linear issues, etc.)
37
+
38
+ **Current Approach:** Use `depends_on` for preprocessing
39
+ ```yaml
40
+ # Verbose and clunky
41
+ steps:
42
+ enrich-jira:
43
+ type: mcp
44
+ method: fetch-jira
45
+ args:
46
+ issue_key: "{{ pr.description | regex_search: '[A-Z]+-[0-9]+' }}"
47
+
48
+ ai-review:
49
+ depends_on: [enrich-jira] # Explicit dependency
50
+ prompt: |
51
+ JIRA: {{ outputs["enrich-jira"] }}
52
+ Review...
53
+ ```
54
+
55
+ **Problems:**
56
+ - ❌ Verbose: Requires separate step definition
57
+ - ❌ Clutters logs: Preprocessor appears as separate step
58
+ - ❌ Not reusable: Hard to share preprocessing logic
59
+ - ❌ Unclear intent: Looks like regular dependency, not preprocessing
60
+ - ❌ Tight coupling: Parent knows about preprocessor step name
61
+
62
+ ### 1.2 Goals
63
+
64
+ 1. **Clean preprocessing** - Make preprocessing intent explicit
65
+ 2. **Reusability** - One helper, many callers with different arguments
66
+ 3. **Composability** - Mix tools, steps, and workflows
67
+ 4. **Consistency** - Follow existing `on_success`/`on_fail`/`on_finish` patterns
68
+ 5. **Flexibility** - Support conditional and dynamic preprocessing
69
+
70
+ ### 1.3 Non-Goals
71
+
72
+ - Replacing `depends_on` for actual dependencies
73
+ - Adding new parameter passing mechanisms (reuse templates)
74
+ - Supporting parallel execution within `on_init`
75
+
76
+ ---
77
+
78
+ ## 2. Proposed Solution
79
+
80
+ ### 2.1 Overview
81
+
82
+ Add an `on_init` lifecycle hook that runs **before** a step executes:
83
+
84
+ ```yaml
85
+ steps:
86
+ ai-review:
87
+ type: ai
88
+ on_init:
89
+ run:
90
+ - tool: fetch-jira
91
+ with:
92
+ issue_key: "{{ pr.description | regex_search: '[A-Z]+-[0-9]+' }}"
93
+ as: jira-context
94
+ prompt: |
95
+ JIRA: {{ outputs["jira-context"] }}
96
+ Review the PR...
97
+
98
+ # Reusable helper (doesn't run independently)
99
+ fetch-jira:
100
+ type: mcp
101
+ method: fetch-jira-ticket
102
+ args:
103
+ issue_key: "{{ args.issue_key }}"
104
+ on: []
105
+ ```
106
+
107
+ ### 2.2 Key Features
108
+
109
+ 1. **`on_init` hook** - Runs before step execution
110
+ 2. **`with` directive** - Pass custom arguments to invoked items
111
+ 3. **`as` directive** - Custom output naming
112
+ 4. **Unified invocations** - Call tools, steps, or workflows
113
+ 5. **`run_js` support** - Dynamic/conditional preprocessing
114
+
115
+ ---
116
+
117
+ ## 3. Detailed Design
118
+
119
+ ### 3.1 Execution Flow
120
+
121
+ ```
122
+ ┌─────────────────────┐
123
+ │ Step Scheduled │
124
+ │ (e.g., ai-review) │
125
+ └──────────┬──────────┘
126
+
127
+
128
+ ┌─────────────────────┐
129
+ │ Check on_init │
130
+ │ directive │
131
+ └──────────┬──────────┘
132
+
133
+
134
+ ┌────┴────┐
135
+ │ Has │
136
+ │ on_init?│
137
+ └────┬────┘
138
+
139
+ Yes │ No
140
+ ┌────┴────┐
141
+ ▼ ▼
142
+ ┌─────────┐ ┌──────────────┐
143
+ │ Execute │ │ Execute Step │
144
+ │ on_init │ │ Normally │
145
+ │ Items │ └──────────────┘
146
+ └────┬────┘
147
+
148
+
149
+ ┌─────────────────────┐
150
+ │ For each item: │
151
+ │ 1. Resolve type │
152
+ │ (tool/step/wflow)│
153
+ │ 2. Inject 'args' │
154
+ │ 3. Execute │
155
+ │ 4. Store output │
156
+ └──────────┬──────────┘
157
+
158
+
159
+ ┌─────────────────────┐
160
+ │ Execute Main Step │
161
+ │ (outputs available) │
162
+ └─────────────────────┘
163
+ ```
164
+
165
+ ### 3.2 Invocation Types
166
+
167
+ The `on_init.run` array supports three invocation types:
168
+
169
+ #### 3.2.1 Tool Invocation
170
+
171
+ ```yaml
172
+ - tool: fetch-jira-ticket
173
+ with:
174
+ issue_key: PROJ-123
175
+ as: jira-data
176
+ ```
177
+
178
+ **Execution:**
179
+ 1. Looks up tool in `tools:` section
180
+ 2. Creates temporary MCP check
181
+ 3. Injects `with` as `args` context
182
+ 4. Executes via MCP provider
183
+ 5. Stores output as `outputs["jira-data"]`
184
+
185
+ #### 3.2.2 Step Invocation
186
+
187
+ ```yaml
188
+ - step: extract-metadata
189
+ with:
190
+ pr_number: "{{ pr.number }}"
191
+ as: metadata
192
+ ```
193
+
194
+ **Execution:**
195
+ 1. Looks up step in `steps:` section
196
+ 2. Injects `with` as `args` context
197
+ 3. Executes step normally
198
+ 4. Stores output as `outputs["metadata"]`
199
+
200
+ #### 3.2.3 Workflow Invocation
201
+
202
+ ```yaml
203
+ - workflow: security-validation
204
+ with:
205
+ files: "{{ files | map: 'filename' }}"
206
+ severity: high
207
+ as: security-results
208
+ overrides:
209
+ scan-step:
210
+ timeout: 120
211
+ ```
212
+
213
+ **Execution:**
214
+ 1. Looks up workflow (registry or file)
215
+ 2. Passes `with` as workflow `inputs`
216
+ 3. Executes workflow via state machine
217
+ 4. Stores output as `outputs["security-results"]`
218
+
219
+ ### 3.3 Argument Passing
220
+
221
+ Arguments flow via the existing template context:
222
+
223
+ ```yaml
224
+ # Caller
225
+ on_init:
226
+ run:
227
+ - tool: fetch-jira
228
+ with:
229
+ issue_key: PROJ-123
230
+ include_comments: true
231
+
232
+ # Tool definition
233
+ tools:
234
+ fetch-jira:
235
+ exec: |
236
+ # Access via args.*
237
+ KEY="{{ args.issue_key }}"
238
+ COMMENTS="{{ args.include_comments | default: false }}"
239
+ curl https://jira.../issue/$KEY
240
+ ```
241
+
242
+ **Template Context Enhancement:**
243
+ ```typescript
244
+ const templateContext = {
245
+ // Standard context
246
+ pr: { ... },
247
+ files: [ ... ],
248
+ outputs: { ... },
249
+ env: { ... },
250
+
251
+ // NEW: Custom arguments from caller
252
+ args: {
253
+ issue_key: "PROJ-123",
254
+ include_comments: true,
255
+ },
256
+ };
257
+ ```
258
+
259
+ ---
260
+
261
+ ## 4. Type Definitions
262
+
263
+ ### 4.1 Core Types
264
+
265
+ ```typescript
266
+ /**
267
+ * Init hook configuration - runs BEFORE check execution
268
+ */
269
+ export interface OnInitConfig {
270
+ /** Items to run before this check executes */
271
+ run?: OnInitRunItem[];
272
+
273
+ /** Dynamic init items: JS expression returning OnInitRunItem[] */
274
+ run_js?: string;
275
+
276
+ /** Declarative transitions (optional, for advanced use cases) */
277
+ transitions?: TransitionRule[];
278
+ }
279
+
280
+ /**
281
+ * Unified run item - can be tool, step, workflow, or plain string
282
+ */
283
+ export type OnInitRunItem =
284
+ | OnInitToolInvocation
285
+ | OnInitStepInvocation
286
+ | OnInitWorkflowInvocation
287
+ | string; // Backward compatible: plain step name
288
+
289
+ /**
290
+ * Invoke a custom tool (from tools: section)
291
+ */
292
+ export interface OnInitToolInvocation {
293
+ /** Tool name (must exist in tools: section) */
294
+ tool: string;
295
+
296
+ /** Arguments to pass to the tool (Liquid templates supported) */
297
+ with?: Record<string, unknown>;
298
+
299
+ /** Custom output name (defaults to tool name) */
300
+ as?: string;
301
+ }
302
+
303
+ /**
304
+ * Invoke a helper step (regular check)
305
+ */
306
+ export interface OnInitStepInvocation {
307
+ /** Step name (must exist in steps: section) */
308
+ step: string;
309
+
310
+ /** Arguments to pass to the step (Liquid templates supported) */
311
+ with?: Record<string, unknown>;
312
+
313
+ /** Custom output name (defaults to step name) */
314
+ as?: string;
315
+ }
316
+
317
+ /**
318
+ * Invoke a reusable workflow
319
+ */
320
+ export interface OnInitWorkflowInvocation {
321
+ /** Workflow ID or path */
322
+ workflow: string;
323
+
324
+ /** Workflow inputs (Liquid templates supported) */
325
+ with?: Record<string, unknown>;
326
+
327
+ /** Custom output name (defaults to workflow name) */
328
+ as?: string;
329
+
330
+ /** Step overrides */
331
+ overrides?: Record<string, Partial<CheckConfig>>;
332
+
333
+ /** Output mapping */
334
+ output_mapping?: Record<string, string>;
335
+ }
336
+ ```
337
+
338
+ ### 4.2 CheckConfig Extension
339
+
340
+ ```typescript
341
+ export interface CheckConfig {
342
+ // ... existing fields ...
343
+
344
+ /** Init routing configuration for this check (runs before execution) */
345
+ on_init?: OnInitConfig;
346
+
347
+ /** Success routing configuration */
348
+ on_success?: OnSuccessConfig;
349
+
350
+ /** Failure routing configuration */
351
+ on_fail?: OnFailConfig;
352
+
353
+ /** Finish routing configuration (forEach only) */
354
+ on_finish?: OnFinishConfig;
355
+ }
356
+ ```
357
+
358
+ ---
359
+
360
+ ## 5. Syntax Specifications
361
+
362
+ ### 5.1 Basic Syntax
363
+
364
+ ```yaml
365
+ # Simple tool invocation
366
+ on_init:
367
+ run:
368
+ - tool: fetch-jira
369
+ with:
370
+ issue_key: PROJ-123
371
+
372
+ # Simple step invocation
373
+ on_init:
374
+ run:
375
+ - step: extract-data
376
+ with:
377
+ text: "{{ pr.description }}"
378
+
379
+ # Simple workflow invocation
380
+ on_init:
381
+ run:
382
+ - workflow: validate
383
+ with:
384
+ env: production
385
+ ```
386
+
387
+ ### 5.2 Mixed Invocations
388
+
389
+ ```yaml
390
+ on_init:
391
+ run:
392
+ # Tool
393
+ - tool: fetch-jira
394
+ with:
395
+ issue_key: PROJ-123
396
+ as: jira
397
+
398
+ # Step
399
+ - step: analyze
400
+ with:
401
+ files: "{{ files }}"
402
+ as: analysis
403
+
404
+ # Workflow
405
+ - workflow: security-scan
406
+ with:
407
+ severity: high
408
+ as: security
409
+ ```
410
+
411
+ ### 5.3 Custom Output Names
412
+
413
+ ```yaml
414
+ on_init:
415
+ run:
416
+ - tool: fetch-jira
417
+ with:
418
+ issue_key: PROJ-100
419
+ as: jira-100
420
+
421
+ - tool: fetch-jira
422
+ with:
423
+ issue_key: PROJ-200
424
+ as: jira-200
425
+
426
+ prompt: |
427
+ JIRA 100: {{ outputs["jira-100"] }}
428
+ JIRA 200: {{ outputs["jira-200"] }}
429
+ ```
430
+
431
+ ### 5.4 Dynamic Invocations
432
+
433
+ ```yaml
434
+ on_init:
435
+ run_js: |
436
+ const items = [];
437
+
438
+ // Conditional tool invocation
439
+ const jiraKey = pr.description?.match(/([A-Z]+-[0-9]+)/)?.[1];
440
+ if (jiraKey) {
441
+ items.push({
442
+ tool: 'fetch-jira',
443
+ with: { issue_key: jiraKey },
444
+ as: 'jira',
445
+ });
446
+ }
447
+
448
+ // Conditional workflow invocation
449
+ if (pr.base === 'main') {
450
+ items.push({
451
+ workflow: 'prod-checks',
452
+ with: { pr_number: pr.number },
453
+ });
454
+ }
455
+
456
+ return items;
457
+ ```
458
+
459
+ ### 5.5 Backward Compatibility
460
+
461
+ ```yaml
462
+ # Old style (still supported)
463
+ on_init:
464
+ run: [step1, step2] # Plain strings
465
+
466
+ # New style
467
+ on_init:
468
+ run:
469
+ - step: step1
470
+ - tool: my-tool
471
+ with:
472
+ key: value
473
+ ```
474
+
475
+ ### 5.6 Template Expressions in Arguments
476
+
477
+ ```yaml
478
+ on_init:
479
+ run:
480
+ - tool: fetch-jira
481
+ with:
482
+ # Liquid template expressions
483
+ issue_key: "{{ pr.description | regex_search: '[A-Z]+-[0-9]+' }}"
484
+ pr_number: "{{ pr.number }}"
485
+ files: "{{ files | map: 'filename' }}"
486
+ has_tests: "{{ files | map: 'filename' | join: ',' | contains: 'test' }}"
487
+ ```
488
+
489
+ ---
490
+
491
+ ## 6. Execution Semantics
492
+
493
+ ### 6.1 Execution Order
494
+
495
+ `on_init` items execute **sequentially** in the order specified:
496
+
497
+ ```yaml
498
+ on_init:
499
+ run:
500
+ - tool: step-1 # Executes first
501
+ - step: step-2 # Executes second (can use outputs["step-1"])
502
+ - workflow: step-3 # Executes third (can use outputs["step-1"] and outputs["step-2"])
503
+ ```
504
+
505
+ ### 6.2 Output Availability
506
+
507
+ Each `on_init` item's output is immediately available to subsequent items:
508
+
509
+ ```yaml
510
+ on_init:
511
+ run:
512
+ - tool: extract-keys
513
+ as: keys
514
+
515
+ - tool: fetch-data
516
+ with:
517
+ keys: "{{ outputs['keys'] }}" # Uses previous output
518
+ as: data
519
+ ```
520
+
521
+ ### 6.3 Error Handling
522
+
523
+ **Default behavior:** If any `on_init` item fails, the parent step fails.
524
+
525
+ ```yaml
526
+ on_init:
527
+ run:
528
+ - tool: fetch-jira
529
+ # If this fails, ai-review doesn't run
530
+ ```
531
+
532
+ **Optional:** Support `continue_on_failure` for specific items:
533
+
534
+ ```yaml
535
+ on_init:
536
+ run:
537
+ - tool: fetch-jira
538
+ with:
539
+ issue_key: "{{ pr.description | regex_search: '[A-Z]+-[0-9]+' }}"
540
+ continue_on_failure: true # Optional enhancement
541
+ ```
542
+
543
+ ### 6.4 Scope Inheritance
544
+
545
+ `on_init` items inherit the parent's forEach scope:
546
+
547
+ ```yaml
548
+ steps:
549
+ validate-files:
550
+ forEach: true
551
+ exec: echo {{ files | map: 'filename' | json }}
552
+
553
+ process-file:
554
+ depends_on: [validate-files]
555
+ on_init:
556
+ run:
557
+ - step: setup-processor
558
+ # Runs once per forEach item, with same scope
559
+ exec: process {{ outputs['validate-files'] }}
560
+ ```
561
+
562
+ ### 6.5 Conditional Execution
563
+
564
+ The parent's `if` condition is evaluated **before** `on_init`:
565
+
566
+ ```yaml
567
+ steps:
568
+ conditional-step:
569
+ if: pr.base === 'main' # Evaluated first
570
+ on_init:
571
+ run: [setup] # Only runs if condition true
572
+ exec: deploy
573
+ ```
574
+
575
+ ---
576
+
577
+ ## 7. Implementation Plan
578
+
579
+ ### 7.1 Phase 1: Core Infrastructure
580
+
581
+ #### File: `src/types/config.ts`
582
+
583
+ **Tasks:**
584
+ - [ ] Add `OnInitConfig` interface
585
+ - [ ] Add `OnInitRunItem` type union
586
+ - [ ] Add `OnInitToolInvocation` interface
587
+ - [ ] Add `OnInitStepInvocation` interface
588
+ - [ ] Add `OnInitWorkflowInvocation` interface
589
+ - [ ] Add `on_init?: OnInitConfig` to `CheckConfig`
590
+
591
+ **Lines:** ~100 new lines
592
+
593
+ #### File: `src/state-machine/dispatch/execution-invoker.ts`
594
+
595
+ **Tasks:**
596
+ - [ ] Add `handleOnInit()` function
597
+ - [ ] Add `executeOnInitItem()` function
598
+ - [ ] Add `detectInvocationType()` function
599
+ - [ ] Add `normalizeRunItems()` function
600
+ - [ ] Call `handleOnInit()` before step execution
601
+ - [ ] Pass enriched context with `args` to helpers
602
+
603
+ **Lines:** ~200-300 new lines
604
+
605
+ **Pseudocode:**
606
+ ```typescript
607
+ async function handleOnInit(
608
+ checkId: string,
609
+ onInit: OnInitConfig,
610
+ context: EngineContext,
611
+ parentScope: Scope
612
+ ): Promise<void> {
613
+ // Process on_init.run
614
+ if (onInit.run && onInit.run.length > 0) {
615
+ const items = normalizeRunItems(onInit.run);
616
+
617
+ for (const item of items) {
618
+ await executeOnInitItem(item, context, parentScope);
619
+ }
620
+ }
621
+
622
+ // Process on_init.run_js
623
+ if (onInit.run_js) {
624
+ const dynamicItems = evaluateRunJs(onInit.run_js, context);
625
+ for (const item of dynamicItems) {
626
+ await executeOnInitItem(item, context, parentScope);
627
+ }
628
+ }
629
+ }
630
+
631
+ async function executeOnInitItem(
632
+ item: OnInitRunItem,
633
+ context: EngineContext,
634
+ scope: Scope
635
+ ): Promise<void> {
636
+ const type = detectInvocationType(item);
637
+ const outputName = item.as || item.tool || item.step || item.workflow;
638
+
639
+ let result: unknown;
640
+
641
+ switch (type) {
642
+ case 'tool':
643
+ result = await executeToolInvocation(item, context, scope);
644
+ break;
645
+ case 'step':
646
+ result = await executeStepInvocation(item, context, scope);
647
+ break;
648
+ case 'workflow':
649
+ result = await executeWorkflowInvocation(item, context, scope);
650
+ break;
651
+ }
652
+
653
+ // Store result for parent and subsequent on_init items
654
+ context.outputs[outputName] = result;
655
+ }
656
+
657
+ function normalizeRunItems(run: OnInitRunItem[]): OnInitRunItem[] {
658
+ return run.map(item => {
659
+ if (typeof item === 'string') {
660
+ return { step: item }; // Backward compat
661
+ }
662
+ return item;
663
+ });
664
+ }
665
+
666
+ function detectInvocationType(item: OnInitRunItem): 'tool' | 'step' | 'workflow' {
667
+ if (typeof item === 'string') return 'step';
668
+ if ('tool' in item) return 'tool';
669
+ if ('workflow' in item) return 'workflow';
670
+ if ('step' in item) return 'step';
671
+ throw new Error('Unknown on_init item type');
672
+ }
673
+ ```
674
+
675
+ ### 7.2 Phase 2: Invocation Handlers
676
+
677
+ #### Tool Invocation
678
+
679
+ ```typescript
680
+ async function executeToolInvocation(
681
+ item: OnInitToolInvocation,
682
+ context: EngineContext,
683
+ scope: Scope
684
+ ): Promise<unknown> {
685
+ const toolDef = context.config.tools?.[item.tool];
686
+ if (!toolDef) {
687
+ throw new Error(`Tool '${item.tool}' not found`);
688
+ }
689
+
690
+ // Create temporary MCP check
691
+ const tempCheck: CheckConfig = {
692
+ type: 'mcp',
693
+ method: item.tool,
694
+ transport: 'custom',
695
+ args: item.with || {},
696
+ };
697
+
698
+ // Build context with args
699
+ const enrichedContext = {
700
+ ...context,
701
+ args: item.with || {},
702
+ };
703
+
704
+ // Execute via MCP provider
705
+ const provider = registry.getProvider('mcp');
706
+ const result = await provider.execute(
707
+ context.prInfo,
708
+ tempCheck,
709
+ buildDependencyResultsWithScope(checkId, tempCheck, context, scope),
710
+ enrichedContext
711
+ );
712
+
713
+ return result.output;
714
+ }
715
+ ```
716
+
717
+ #### Step Invocation
718
+
719
+ ```typescript
720
+ async function executeStepInvocation(
721
+ item: OnInitStepInvocation,
722
+ context: EngineContext,
723
+ scope: Scope
724
+ ): Promise<unknown> {
725
+ const stepConfig = context.config.steps?.[item.step];
726
+ if (!stepConfig) {
727
+ throw new Error(`Step '${item.step}' not found`);
728
+ }
729
+
730
+ // Build context with args
731
+ const enrichedContext = {
732
+ ...context,
733
+ args: item.with || {},
734
+ };
735
+
736
+ // Execute step
737
+ const provider = registry.getProvider(stepConfig.type || 'ai');
738
+ const result = await provider.execute(
739
+ context.prInfo,
740
+ stepConfig,
741
+ buildDependencyResultsWithScope(checkId, stepConfig, context, scope),
742
+ enrichedContext
743
+ );
744
+
745
+ return result.output;
746
+ }
747
+ ```
748
+
749
+ #### Workflow Invocation
750
+
751
+ ```typescript
752
+ async function executeWorkflowInvocation(
753
+ item: OnInitWorkflowInvocation,
754
+ context: EngineContext,
755
+ scope: Scope
756
+ ): Promise<unknown> {
757
+ // Create workflow check
758
+ const workflowCheck: CheckConfig = {
759
+ type: 'workflow',
760
+ workflow: item.workflow,
761
+ args: item.with || {},
762
+ overrides: item.overrides,
763
+ output_mapping: item.output_mapping,
764
+ };
765
+
766
+ // Execute via workflow provider
767
+ const provider = registry.getProvider('workflow');
768
+ const result = await provider.execute(
769
+ context.prInfo,
770
+ workflowCheck,
771
+ buildDependencyResultsWithScope(checkId, workflowCheck, context, scope),
772
+ context
773
+ );
774
+
775
+ return result.output;
776
+ }
777
+ ```
778
+
779
+ ### 7.3 Phase 3: Context Enhancement
780
+
781
+ #### File: `src/providers/command-check-provider.ts`
782
+
783
+ **Tasks:**
784
+ - [ ] Add `args` to template context
785
+ - [ ] Pass `args` from `context?.args`
786
+
787
+ **Changes:**
788
+ ```typescript
789
+ const templateContext = {
790
+ pr: { ... },
791
+ files: [ ... ],
792
+ outputs: { ... },
793
+ env: { ... },
794
+
795
+ // NEW: Custom arguments
796
+ args: context?.args || {},
797
+ };
798
+ ```
799
+
800
+ **Files to update:**
801
+ - `src/providers/ai-check-provider.ts`
802
+ - `src/providers/command-check-provider.ts`
803
+ - `src/providers/mcp-check-provider.ts`
804
+ - `src/providers/http-check-provider.ts`
805
+ - All providers that render templates
806
+
807
+ ### 7.4 Phase 4: Routing & Loop Budget
808
+
809
+ #### File: `src/state-machine/states/routing.ts`
810
+
811
+ **Tasks:**
812
+ - [ ] Add loop budget checking for `on_init`
813
+ - [ ] Increment routing loop counter
814
+ - [ ] Add `on_init` to routing metrics
815
+
816
+ **Changes:**
817
+ ```typescript
818
+ // Check loop budget
819
+ if (checkLoopBudget(context, state, 'on_init', 'run')) {
820
+ throw new Error(`Loop budget exceeded during on_init`);
821
+ }
822
+
823
+ // Increment counter
824
+ incrementRoutingLoopCount(context, state, 'on_init', 'run');
825
+ ```
826
+
827
+ ### 7.5 Phase 5: Testing
828
+
829
+ #### Unit Tests
830
+
831
+ **File:** `tests/unit/on-init-handler.test.ts`
832
+ - [ ] Test `normalizeRunItems()`
833
+ - [ ] Test `detectInvocationType()`
834
+ - [ ] Test `handleOnInit()` with tools
835
+ - [ ] Test `handleOnInit()` with steps
836
+ - [ ] Test `handleOnInit()` with workflows
837
+ - [ ] Test mixed invocations
838
+ - [ ] Test `with` argument passing
839
+ - [ ] Test `as` output naming
840
+ - [ ] Test error handling
841
+ - [ ] Test loop budget enforcement
842
+
843
+ #### Integration Tests
844
+
845
+ **File:** `tests/integration/on-init.test.ts`
846
+ - [ ] Test tool invocation end-to-end
847
+ - [ ] Test step invocation end-to-end
848
+ - [ ] Test workflow invocation end-to-end
849
+ - [ ] Test argument flow (with → args)
850
+ - [ ] Test output availability
851
+ - [ ] Test chaining (tool → step → workflow)
852
+ - [ ] Test conditional execution (run_js)
853
+ - [ ] Test forEach scope inheritance
854
+ - [ ] Test backward compatibility (string arrays)
855
+
856
+ #### E2E Tests
857
+
858
+ **File:** `tests/e2e/on-init-jira.test.ts`
859
+ - [ ] Test JIRA link preprocessing
860
+ - [ ] Test multiple link types (JIRA + Linear)
861
+ - [ ] Test dynamic preprocessing
862
+ - [ ] Test with real workflows
863
+
864
+ ### 7.6 Phase 6: Documentation
865
+
866
+ **Tasks:**
867
+ - [ ] Update `CLAUDE.md` with `on_init` pattern
868
+ - [ ] Add examples to `examples/` directory
869
+ - [ ] Update API documentation
870
+ - [ ] Add troubleshooting guide
871
+ - [ ] Create migration guide from `depends_on`
872
+
873
+ **Files:**
874
+ - `docs/on_init-guide.md` - User guide
875
+ - `docs/on_init-api.md` - API reference
876
+ - `examples/on_init-*.yaml` - Examples
877
+ - `CLAUDE.md` - Update with on_init pattern
878
+
879
+ ### 7.7 Phase 7: Schema Generation
880
+
881
+ **File:** `src/generated/config-schema.ts`
882
+
883
+ **Tasks:**
884
+ - [ ] Run schema generator
885
+ - [ ] Validate generated types
886
+ - [ ] Update JSON schema
887
+
888
+ ---
889
+
890
+ ## 8. Examples
891
+
892
+ ### 8.1 Basic JIRA Preprocessing
893
+
894
+ ```yaml
895
+ tools:
896
+ fetch-jira:
897
+ exec: curl https://jira.../issue/{{ args.issue_key }}
898
+ parseJson: true
899
+ transform_js: |
900
+ return `<jira>${output.fields.summary}</jira>`;
901
+
902
+ steps:
903
+ ai-review:
904
+ type: ai
905
+ on_init:
906
+ run:
907
+ - tool: fetch-jira
908
+ with:
909
+ issue_key: "{{ pr.description | regex_search: '[A-Z]+-[0-9]+' }}"
910
+ as: jira-context
911
+ prompt: |
912
+ JIRA: {{ outputs["jira-context"] }}
913
+ Review the PR...
914
+
915
+ fetch-jira:
916
+ type: mcp
917
+ method: fetch-jira
918
+ transport: custom
919
+ args:
920
+ issue_key: "{{ args.issue_key }}"
921
+ on: []
922
+ ```
923
+
924
+ ### 8.2 Multi-Source Enrichment
925
+
926
+ ```yaml
927
+ steps:
928
+ comprehensive-review:
929
+ type: ai
930
+ on_init:
931
+ run_js: |
932
+ const items = [];
933
+
934
+ // JIRA
935
+ const jiraKey = pr.description?.match(/([A-Z]+-[0-9]+)/)?.[1];
936
+ if (jiraKey) {
937
+ items.push({
938
+ tool: 'fetch-jira',
939
+ with: { issue_key: jiraKey },
940
+ as: 'jira',
941
+ });
942
+ }
943
+
944
+ // Linear
945
+ const linearUrl = pr.description?.match(/(https:\/\/linear\.app\/[^\s]+)/)?.[1];
946
+ if (linearUrl) {
947
+ items.push({
948
+ tool: 'fetch-linear',
949
+ with: { url: linearUrl },
950
+ as: 'linear',
951
+ });
952
+ }
953
+
954
+ // Security scan (workflow)
955
+ if (pr.base === 'main') {
956
+ items.push({
957
+ workflow: 'security-scan',
958
+ with: { files: files.map(f => f.filename) },
959
+ as: 'security',
960
+ });
961
+ }
962
+
963
+ return items;
964
+
965
+ prompt: |
966
+ {% if outputs["jira"] %}
967
+ JIRA: {{ outputs["jira"] }}
968
+ {% endif %}
969
+
970
+ {% if outputs["linear"] %}
971
+ Linear: {{ outputs["linear"] }}
972
+ {% endif %}
973
+
974
+ {% if outputs["security"] %}
975
+ Security: {{ outputs["security"] }}
976
+ {% endif %}
977
+
978
+ Review...
979
+ ```
980
+
981
+ ### 8.3 Setup/Teardown Pattern
982
+
983
+ ```yaml
984
+ steps:
985
+ integration-test:
986
+ type: command
987
+ on_init:
988
+ run:
989
+ - step: setup-database
990
+ with:
991
+ name: test-db
992
+ on_finish:
993
+ run:
994
+ - step: cleanup-database
995
+ with:
996
+ name: test-db
997
+ exec: npm run test:integration
998
+
999
+ setup-database:
1000
+ type: command
1001
+ exec: docker run -d --name {{ args.name }} postgres
1002
+ on: []
1003
+
1004
+ cleanup-database:
1005
+ type: command
1006
+ exec: docker rm -f {{ args.name }}
1007
+ on: []
1008
+ ```
1009
+
1010
+ ---
1011
+
1012
+ ## 9. Alternatives Considered
1013
+
1014
+ ### 9.1 Alternative 1: `preprocess` Directive
1015
+
1016
+ ```yaml
1017
+ steps:
1018
+ ai-review:
1019
+ preprocess: [fetch-jira]
1020
+ ```
1021
+
1022
+ **Rejected:** Less consistent with existing `on_*` pattern.
1023
+
1024
+ ### 9.2 Alternative 2: `setup` Hook
1025
+
1026
+ ```yaml
1027
+ steps:
1028
+ ai-review:
1029
+ setup:
1030
+ run: [fetch-jira]
1031
+ ```
1032
+
1033
+ **Rejected:** "setup" implies infrastructure, not data enrichment.
1034
+
1035
+ ### 9.3 Alternative 3: Automatic Detection
1036
+
1037
+ ```yaml
1038
+ steps:
1039
+ ai-review:
1040
+ prompt: |
1041
+ {{ enrich("jira", pr.description) }}
1042
+ ```
1043
+
1044
+ **Rejected:** Too magical, unclear execution order, hard to debug.
1045
+
1046
+ ### 9.4 Alternative 4: Context Enrichers (Global)
1047
+
1048
+ ```yaml
1049
+ context_enrichers:
1050
+ jira:
1051
+ pattern: 'https://.*atlassian.net/browse/([A-Z]+-[0-9]+)'
1052
+ tool: fetch-jira
1053
+ ```
1054
+
1055
+ **Rejected:** Less flexible, no per-step control, new top-level concept.
1056
+
1057
+ ### 9.5 Alternative 5: Inline Syntax
1058
+
1059
+ ```yaml
1060
+ on_init:
1061
+ run: [fetch-jira(issue_key=PROJ-123)]
1062
+ ```
1063
+
1064
+ **Rejected:** Non-standard YAML, harder to parse, limited.
1065
+
1066
+ ---
1067
+
1068
+ ## 10. Migration Path
1069
+
1070
+ ### 10.1 From `depends_on` to `on_init`
1071
+
1072
+ **Before:**
1073
+ ```yaml
1074
+ steps:
1075
+ enrich-jira:
1076
+ type: mcp
1077
+ method: fetch-jira
1078
+
1079
+ ai-review:
1080
+ depends_on: [enrich-jira]
1081
+ prompt: "JIRA: {{ outputs['enrich-jira'] }}"
1082
+ ```
1083
+
1084
+ **After:**
1085
+ ```yaml
1086
+ steps:
1087
+ ai-review:
1088
+ on_init:
1089
+ run:
1090
+ - step: enrich-jira
1091
+ prompt: "JIRA: {{ outputs['enrich-jira'] }}"
1092
+
1093
+ enrich-jira:
1094
+ type: mcp
1095
+ method: fetch-jira
1096
+ on: [] # Mark as helper only
1097
+ ```
1098
+
1099
+ ### 10.2 Backward Compatibility
1100
+
1101
+ **Existing configs continue to work:**
1102
+ ```yaml
1103
+ on_init:
1104
+ run: [step1, step2] # String array (backward compat)
1105
+ ```
1106
+
1107
+ **New syntax is opt-in:**
1108
+ ```yaml
1109
+ on_init:
1110
+ run:
1111
+ - tool: my-tool
1112
+ with: { ... }
1113
+ ```
1114
+
1115
+ ---
1116
+
1117
+ ## 11. Open Questions
1118
+
1119
+ ### 11.1 Parallel Execution
1120
+
1121
+ **Question:** Should `on_init` items execute in parallel or sequentially?
1122
+
1123
+ **Proposal:** Sequential (preserves output ordering, simpler)
1124
+
1125
+ **Future:** Add `parallel: true` option if needed:
1126
+ ```yaml
1127
+ on_init:
1128
+ run:
1129
+ - tool: fetch-jira
1130
+ - tool: fetch-linear
1131
+ parallel: true # Future enhancement
1132
+ ```
1133
+
1134
+ ### 11.2 Nested on_init
1135
+
1136
+ **Question:** Can `on_init` steps have their own `on_init`?
1137
+
1138
+ **Proposal:** Yes, but enforce max depth (e.g., 3 levels) to prevent infinite loops.
1139
+
1140
+ ### 11.3 Continue on Failure
1141
+
1142
+ **Question:** Should individual `on_init` items support `continue_on_failure`?
1143
+
1144
+ **Proposal:** Future enhancement:
1145
+ ```yaml
1146
+ on_init:
1147
+ run:
1148
+ - tool: fetch-jira
1149
+ continue_on_failure: true
1150
+ ```
1151
+
1152
+ ### 11.4 Output Scoping
1153
+
1154
+ **Question:** Should `on_init` outputs be scoped differently from regular outputs?
1155
+
1156
+ **Proposal:** No, use the same `outputs` map for consistency.
1157
+
1158
+ ### 11.5 Timeout
1159
+
1160
+ **Question:** Should `on_init` have a global timeout?
1161
+
1162
+ **Proposal:** Use sum of individual item timeouts. Add `timeout` if needed:
1163
+ ```yaml
1164
+ on_init:
1165
+ timeout: 60000 # ms (future enhancement)
1166
+ run: [ ... ]
1167
+ ```
1168
+
1169
+ ---
1170
+
1171
+ ## 12. Summary
1172
+
1173
+ ### 12.1 Benefits
1174
+
1175
+ 1. **✅ Clean preprocessing** - Explicit intent, doesn't clutter logs
1176
+ 2. **✅ Reusable** - One helper, many callers with `with` args
1177
+ 3. **✅ Unified** - Tools, steps, workflows with same syntax
1178
+ 4. **✅ Consistent** - Follows existing `on_*` patterns
1179
+ 5. **✅ Flexible** - Conditional, dynamic, composable
1180
+ 6. **✅ Backward compatible** - Existing code works
1181
+
1182
+ ### 12.2 Implementation Effort
1183
+
1184
+ | Phase | Effort | Lines of Code |
1185
+ |-------|--------|---------------|
1186
+ | Type definitions | Low | ~100 |
1187
+ | Core handler | Medium | ~300 |
1188
+ | Invocation handlers | Medium | ~200 |
1189
+ | Context enhancement | Low | ~50 |
1190
+ | Testing | High | ~500 |
1191
+ | Documentation | Medium | ~1000 |
1192
+ | **Total** | **Medium-High** | **~2150** |
1193
+
1194
+ ### 12.3 Rollout Plan
1195
+
1196
+ 1. **Week 1:** Type definitions + schema
1197
+ 2. **Week 2:** Core implementation
1198
+ 3. **Week 3:** Testing
1199
+ 4. **Week 4:** Documentation + examples
1200
+ 5. **Week 5:** Beta testing with real workflows
1201
+ 6. **Week 6:** GA release
1202
+
1203
+ ---
1204
+
1205
+ ## 13. References
1206
+
1207
+ - [Existing `on_success` implementation](src/state-machine/states/routing.ts#599)
1208
+ - [Existing `on_fail` implementation](src/state-machine/states/routing.ts#816)
1209
+ - [Existing `on_finish` implementation](src/state-machine/states/routing.ts#231)
1210
+ - [Workflow provider](src/providers/workflow-check-provider.ts)
1211
+ - [Custom tool executor](src/providers/custom-tool-executor.ts)
1212
+
1213
+ ---
1214
+
1215
+ ## Appendix A: Complete Type Definitions
1216
+
1217
+ ```typescript
1218
+ // src/types/config.ts
1219
+
1220
+ /**
1221
+ * Init hook configuration - runs BEFORE check execution
1222
+ */
1223
+ export interface OnInitConfig {
1224
+ /** Items to run before this check executes */
1225
+ run?: OnInitRunItem[];
1226
+
1227
+ /** Dynamic init items: JS expression returning OnInitRunItem[] */
1228
+ run_js?: string;
1229
+
1230
+ /** Declarative transitions (optional) */
1231
+ transitions?: TransitionRule[];
1232
+ }
1233
+
1234
+ /**
1235
+ * Unified run item
1236
+ */
1237
+ export type OnInitRunItem =
1238
+ | OnInitToolInvocation
1239
+ | OnInitStepInvocation
1240
+ | OnInitWorkflowInvocation
1241
+ | string;
1242
+
1243
+ /**
1244
+ * Tool invocation
1245
+ */
1246
+ export interface OnInitToolInvocation {
1247
+ tool: string;
1248
+ with?: Record<string, unknown>;
1249
+ as?: string;
1250
+ }
1251
+
1252
+ /**
1253
+ * Step invocation
1254
+ */
1255
+ export interface OnInitStepInvocation {
1256
+ step: string;
1257
+ with?: Record<string, unknown>;
1258
+ as?: string;
1259
+ }
1260
+
1261
+ /**
1262
+ * Workflow invocation
1263
+ */
1264
+ export interface OnInitWorkflowInvocation {
1265
+ workflow: string;
1266
+ with?: Record<string, unknown>;
1267
+ as?: string;
1268
+ overrides?: Record<string, Partial<CheckConfig>>;
1269
+ output_mapping?: Record<string, string>;
1270
+ }
1271
+
1272
+ /**
1273
+ * Check configuration (extended)
1274
+ */
1275
+ export interface CheckConfig {
1276
+ // ... existing fields ...
1277
+
1278
+ /** Init hook - runs before check execution */
1279
+ on_init?: OnInitConfig;
1280
+
1281
+ /** Success hook */
1282
+ on_success?: OnSuccessConfig;
1283
+
1284
+ /** Failure hook */
1285
+ on_fail?: OnFailConfig;
1286
+
1287
+ /** Finish hook (forEach only) */
1288
+ on_finish?: OnFinishConfig;
1289
+ }
1290
+ ```
1291
+
1292
+ ---
1293
+
1294
+ **End of RFC**