@probelabs/visor 0.1.113 → 0.1.122

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 (120) hide show
  1. package/defaults/workflow-builder.tests.yaml +363 -0
  2. package/defaults/workflow-builder.yaml +720 -0
  3. package/dist/ai-review-service.d.ts.map +1 -1
  4. package/dist/cli-main.d.ts.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/defaults/workflow-builder.tests.yaml +363 -0
  7. package/dist/defaults/workflow-builder.yaml +720 -0
  8. package/dist/docs/workflow-creation-guide.md +1274 -0
  9. package/dist/frontends/slack-frontend.d.ts +3 -0
  10. package/dist/frontends/slack-frontend.d.ts.map +1 -1
  11. package/dist/generated/config-schema.d.ts +14 -6
  12. package/dist/generated/config-schema.d.ts.map +1 -1
  13. package/dist/generated/config-schema.json +14 -6
  14. package/dist/index.js +36879 -13895
  15. package/dist/logger.d.ts +9 -0
  16. package/dist/logger.d.ts.map +1 -1
  17. package/dist/mcp-server.d.ts +4 -4
  18. package/dist/output/traces/{run-2026-01-21T12-31-10-108Z.ndjson → run-2026-01-28T13-56-32-263Z.ndjson} +84 -84
  19. package/dist/output/traces/run-2026-01-28T13-57-13-140Z.ndjson +1357 -0
  20. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  21. package/dist/providers/check-provider.interface.d.ts +15 -1
  22. package/dist/providers/check-provider.interface.d.ts.map +1 -1
  23. package/dist/providers/command-check-provider.d.ts.map +1 -1
  24. package/dist/providers/git-checkout-provider.d.ts.map +1 -1
  25. package/dist/sdk/{check-provider-registry-534KL5HT.mjs → check-provider-registry-JMNLGIMJ.mjs} +11 -11
  26. package/dist/sdk/{chunk-AIVFBIS4.mjs → chunk-35NT3725.mjs} +30 -10
  27. package/dist/sdk/chunk-35NT3725.mjs.map +1 -0
  28. package/dist/sdk/{chunk-AGIZJ4UZ.mjs → chunk-3NMLT3YS.mjs} +42 -8
  29. package/dist/sdk/chunk-3NMLT3YS.mjs.map +1 -0
  30. package/dist/sdk/{chunk-AK6BVWIT.mjs → chunk-7GUAFV6L.mjs} +2 -2
  31. package/dist/sdk/{chunk-QY2XYPEV.mjs → chunk-CUNPH6TR.mjs} +18 -10
  32. package/dist/sdk/chunk-CUNPH6TR.mjs.map +1 -0
  33. package/dist/sdk/{chunk-HTOKWMPO.mjs → chunk-HQL734ZI.mjs} +2 -2
  34. package/dist/sdk/{chunk-7UK3NIIT.mjs → chunk-IHZOSIF4.mjs} +2 -2
  35. package/dist/sdk/{chunk-AUT26LHW.mjs → chunk-J2QWVDXK.mjs} +2 -2
  36. package/dist/sdk/{chunk-QR7MOMJH.mjs → chunk-J6EVEXC2.mjs} +2 -2
  37. package/dist/sdk/{chunk-SIWNBRTK.mjs → chunk-SWEEZ5D5.mjs} +3 -3
  38. package/dist/sdk/{chunk-23L3QRYX.mjs → chunk-VPEQOQ7G.mjs} +279 -70
  39. package/dist/sdk/chunk-VPEQOQ7G.mjs.map +1 -0
  40. package/dist/sdk/{command-executor-TYUV6HUS.mjs → command-executor-Q7MHJKZJ.mjs} +3 -3
  41. package/dist/sdk/{config-YNC2EOOT.mjs → config-MK4XTU45.mjs} +3 -3
  42. package/dist/sdk/{failure-condition-evaluator-YGTF2GHG.mjs → failure-condition-evaluator-HB35XRLZ.mjs} +4 -4
  43. package/dist/sdk/{github-frontend-SIAEOCON.mjs → github-frontend-6Q4BISZX.mjs} +4 -4
  44. package/dist/sdk/{host-DXUYTNMU.mjs → host-P5NQICP7.mjs} +3 -3
  45. package/dist/sdk/{liquid-extensions-PKWCKK7E.mjs → liquid-extensions-DFDEBMUI.mjs} +4 -4
  46. package/dist/sdk/{memory-store-XGBB7LX7.mjs → memory-store-RW5N2NGJ.mjs} +3 -3
  47. package/dist/sdk/{prompt-state-YRJY6QAL.mjs → prompt-state-EZYOUG75.mjs} +3 -3
  48. package/dist/sdk/{renderer-schema-LPKN5UJS.mjs → renderer-schema-CKFB5NDB.mjs} +2 -2
  49. package/dist/sdk/{routing-6N45MJ4F.mjs → routing-KZ345OFG.mjs} +5 -5
  50. package/dist/sdk/sdk.d.mts +19 -1
  51. package/dist/sdk/sdk.d.ts +19 -1
  52. package/dist/sdk/sdk.js +421 -75
  53. package/dist/sdk/sdk.js.map +1 -1
  54. package/dist/sdk/sdk.mjs +35 -18
  55. package/dist/sdk/sdk.mjs.map +1 -1
  56. package/dist/sdk/{slack-frontend-BVKW3GD5.mjs → slack-frontend-J442FJWZ.mjs} +61 -3
  57. package/dist/sdk/slack-frontend-J442FJWZ.mjs.map +1 -0
  58. package/dist/sdk/{workflow-registry-R6KSACFR.mjs → workflow-registry-6LZKCWHP.mjs} +3 -3
  59. package/dist/state-machine/context/build-engine-context.d.ts.map +1 -1
  60. package/dist/state-machine/dispatch/history-snapshot.d.ts.map +1 -1
  61. package/dist/state-machine/runner.d.ts.map +1 -1
  62. package/dist/state-machine/states/level-dispatch.d.ts.map +1 -1
  63. package/dist/state-machine/states/routing.d.ts.map +1 -1
  64. package/dist/state-machine/states/wave-planning.d.ts.map +1 -1
  65. package/dist/ter-u14b.json +17826 -0
  66. package/dist/ter-u14n.json +17826 -0
  67. package/dist/test-runner/index.d.ts.map +1 -1
  68. package/dist/traces/{run-2026-01-21T12-31-10-108Z.ndjson → run-2026-01-28T13-56-32-263Z.ndjson} +84 -84
  69. package/dist/traces/run-2026-01-28T13-57-13-140Z.ndjson +1357 -0
  70. package/dist/tui.d.ts +51 -0
  71. package/dist/tui.d.ts.map +1 -0
  72. package/dist/types/cli.d.ts +10 -0
  73. package/dist/types/cli.d.ts.map +1 -1
  74. package/dist/types/config.d.ts +4 -0
  75. package/dist/types/config.d.ts.map +1 -1
  76. package/dist/types/engine.d.ts +3 -0
  77. package/dist/types/engine.d.ts.map +1 -1
  78. package/dist/usr/fonts/AUTHORS +1 -0
  79. package/dist/usr/fonts/LICENSE +94 -0
  80. package/dist/usr/fonts/README +340 -0
  81. package/dist/usr/fonts/ter-u14b.json +17826 -0
  82. package/dist/usr/fonts/ter-u14n.json +17826 -0
  83. package/dist/usr/linux +0 -0
  84. package/dist/usr/windows-ansi +0 -0
  85. package/dist/usr/xterm +0 -0
  86. package/dist/usr/xterm-256color +0 -0
  87. package/dist/usr/xterm.termcap +243 -0
  88. package/dist/usr/xterm.terminfo +1977 -0
  89. package/dist/utils/workspace-manager.d.ts +2 -0
  90. package/dist/utils/workspace-manager.d.ts.map +1 -1
  91. package/dist/utils/worktree-manager.d.ts +5 -0
  92. package/dist/utils/worktree-manager.d.ts.map +1 -1
  93. package/dist/xterm +0 -0
  94. package/dist/xterm.termcap +243 -0
  95. package/package.json +9 -7
  96. package/dist/output/traces/run-2026-01-21T12-32-04-510Z.ndjson +0 -1067
  97. package/dist/sdk/chunk-23L3QRYX.mjs.map +0 -1
  98. package/dist/sdk/chunk-AGIZJ4UZ.mjs.map +0 -1
  99. package/dist/sdk/chunk-AIVFBIS4.mjs.map +0 -1
  100. package/dist/sdk/chunk-QY2XYPEV.mjs.map +0 -1
  101. package/dist/sdk/slack-frontend-BVKW3GD5.mjs.map +0 -1
  102. package/dist/traces/run-2026-01-21T12-32-04-510Z.ndjson +0 -1067
  103. /package/dist/sdk/{check-provider-registry-534KL5HT.mjs.map → check-provider-registry-JMNLGIMJ.mjs.map} +0 -0
  104. /package/dist/sdk/{chunk-AK6BVWIT.mjs.map → chunk-7GUAFV6L.mjs.map} +0 -0
  105. /package/dist/sdk/{chunk-HTOKWMPO.mjs.map → chunk-HQL734ZI.mjs.map} +0 -0
  106. /package/dist/sdk/{chunk-7UK3NIIT.mjs.map → chunk-IHZOSIF4.mjs.map} +0 -0
  107. /package/dist/sdk/{chunk-AUT26LHW.mjs.map → chunk-J2QWVDXK.mjs.map} +0 -0
  108. /package/dist/sdk/{chunk-QR7MOMJH.mjs.map → chunk-J6EVEXC2.mjs.map} +0 -0
  109. /package/dist/sdk/{chunk-SIWNBRTK.mjs.map → chunk-SWEEZ5D5.mjs.map} +0 -0
  110. /package/dist/sdk/{command-executor-TYUV6HUS.mjs.map → command-executor-Q7MHJKZJ.mjs.map} +0 -0
  111. /package/dist/sdk/{config-YNC2EOOT.mjs.map → config-MK4XTU45.mjs.map} +0 -0
  112. /package/dist/sdk/{failure-condition-evaluator-YGTF2GHG.mjs.map → failure-condition-evaluator-HB35XRLZ.mjs.map} +0 -0
  113. /package/dist/sdk/{github-frontend-SIAEOCON.mjs.map → github-frontend-6Q4BISZX.mjs.map} +0 -0
  114. /package/dist/sdk/{host-DXUYTNMU.mjs.map → host-P5NQICP7.mjs.map} +0 -0
  115. /package/dist/sdk/{liquid-extensions-PKWCKK7E.mjs.map → liquid-extensions-DFDEBMUI.mjs.map} +0 -0
  116. /package/dist/sdk/{memory-store-XGBB7LX7.mjs.map → memory-store-RW5N2NGJ.mjs.map} +0 -0
  117. /package/dist/sdk/{prompt-state-YRJY6QAL.mjs.map → prompt-state-EZYOUG75.mjs.map} +0 -0
  118. /package/dist/sdk/{renderer-schema-LPKN5UJS.mjs.map → renderer-schema-CKFB5NDB.mjs.map} +0 -0
  119. /package/dist/sdk/{routing-6N45MJ4F.mjs.map → routing-KZ345OFG.mjs.map} +0 -0
  120. /package/dist/sdk/{workflow-registry-R6KSACFR.mjs.map → workflow-registry-6LZKCWHP.mjs.map} +0 -0
@@ -0,0 +1,1274 @@
1
+ # Visor Workflow Creation Guide
2
+
3
+ This guide provides comprehensive instructions for creating Visor workflows. It covers the structure, available check types, configuration patterns, testing DSL, and best practices.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Workflow Structure](#workflow-structure)
8
+ 2. [Check Types Reference](#check-types-reference)
9
+ 3. [Configuration Patterns](#configuration-patterns)
10
+ 4. [Testing DSL](#testing-dsl)
11
+ 5. [Style Guide](#style-guide)
12
+ 6. [Example Patterns](#example-patterns)
13
+ 7. [Common Pitfalls](#common-pitfalls)
14
+
15
+ ---
16
+
17
+ ## Workflow Structure
18
+
19
+ ### Basic Structure
20
+
21
+ Every Visor workflow follows this structure:
22
+
23
+ ```yaml
24
+ version: "1.0"
25
+
26
+ # Optional: Workflow metadata
27
+ id: my-workflow
28
+ name: My Workflow Name
29
+ description: What this workflow does
30
+
31
+ # Optional: Global routing configuration
32
+ routing:
33
+ max_loops: 5 # Prevent infinite routing loops
34
+
35
+ # Optional: Workflow-level outputs
36
+ outputs:
37
+ - name: result
38
+ description: The aggregated result
39
+ value_js: |
40
+ const all = Object.values(outputs || {});
41
+ return all.map(v => v?.issues || []).flat();
42
+
43
+ # Required: Steps definition
44
+ steps:
45
+ step-one:
46
+ type: ai
47
+ prompt: "Analyze the code"
48
+ # ... step configuration
49
+
50
+ step-two:
51
+ type: command
52
+ depends_on: [step-one]
53
+ exec: "echo done"
54
+
55
+ # Optional: Inline tests
56
+ tests:
57
+ defaults:
58
+ strict: true
59
+ ai_provider: mock
60
+ cases:
61
+ - name: basic-flow
62
+ event: manual
63
+ # ... test case configuration
64
+ ```
65
+
66
+ ### Key Sections
67
+
68
+ | Section | Required | Description |
69
+ |---------|----------|-------------|
70
+ | `version` | Yes | Always `"1.0"` |
71
+ | `steps` | Yes | Map of step names to configurations |
72
+ | `outputs` | No | Workflow-level output definitions |
73
+ | `routing` | No | Global routing configuration |
74
+ | `tests` | No | Inline test cases (or use separate `.tests.yaml`) |
75
+ | `imports` | No | External workflow files to import |
76
+
77
+ ### Event Triggers
78
+
79
+ Steps can declare which events trigger them:
80
+
81
+ ```yaml
82
+ steps:
83
+ on-pr-open:
84
+ type: ai
85
+ on: [pr_opened] # Only on PR open
86
+
87
+ on-pr-changes:
88
+ type: ai
89
+ on: [pr_opened, pr_updated] # PR open or update
90
+
91
+ on-manual:
92
+ type: command
93
+ # No 'on:' means manual-only by default
94
+ ```
95
+
96
+ Available events:
97
+ - `pr_opened` - Pull request opened
98
+ - `pr_updated` - Pull request synchronized/updated
99
+ - `pr_closed` - Pull request closed
100
+ - `issue_opened` - Issue created
101
+ - `issue_comment` - Comment on issue or PR
102
+ - `manual` - CLI execution (no event)
103
+
104
+ ---
105
+
106
+ ## Check Types Reference
107
+
108
+ ### 1. AI Check (`type: ai`)
109
+
110
+ AI-powered analysis using LLMs (Gemini, Claude, OpenAI).
111
+
112
+ ```yaml
113
+ steps:
114
+ analyze:
115
+ type: ai
116
+ on: [pr_opened, pr_updated]
117
+
118
+ # The prompt sent to the AI
119
+ prompt: |
120
+ Analyze the code changes for security issues.
121
+
122
+ Files changed: {{ files | json }}
123
+ PR Title: {{ pr.title }}
124
+
125
+ # Output schema (JSON Schema or named schema)
126
+ schema: code-review # Named schema
127
+ # OR inline schema:
128
+ schema:
129
+ type: object
130
+ properties:
131
+ issues:
132
+ type: array
133
+ items:
134
+ type: object
135
+ properties:
136
+ severity: { type: string, enum: [critical, error, warning, info] }
137
+ message: { type: string }
138
+ required: [severity, message]
139
+ required: [issues]
140
+
141
+ # AI configuration
142
+ ai:
143
+ provider: anthropic # google, anthropic, openai
144
+ model: claude-3-opus-20240229
145
+ skip_code_context: false # Include code context in prompt
146
+ disableTools: false # Allow tool use
147
+ system_prompt: |
148
+ You are a security expert.
149
+ ```
150
+
151
+ ### 2. Claude Code Check (`type: claude-code`)
152
+
153
+ Advanced AI with MCP tools, file editing, and subagents.
154
+
155
+ ```yaml
156
+ steps:
157
+ comprehensive-analysis:
158
+ type: claude-code
159
+ prompt: |
160
+ Perform a comprehensive code review:
161
+ {{ outputs['get-requirements'].text }}
162
+
163
+ claude_code:
164
+ allowedTools: ['Read', 'Grep', 'Edit', 'Write', 'Bash']
165
+ maxTurns: 10
166
+ systemPrompt: |
167
+ You are an expert code reviewer.
168
+
169
+ # Bash permissions
170
+ allowBash: true
171
+ bashConfig:
172
+ allow:
173
+ - 'npm test'
174
+ - 'npm run lint'
175
+ - 'git status'
176
+ - 'git diff'
177
+ deny:
178
+ - 'rm -rf'
179
+ - 'git push'
180
+
181
+ # MCP server configuration
182
+ mcpServers:
183
+ analyzer:
184
+ command: "node"
185
+ args: ["./tools/analyzer.js"]
186
+ env:
187
+ MODE: "deep"
188
+ ```
189
+
190
+ ### 3. Command Check (`type: command`)
191
+
192
+ Execute shell commands.
193
+
194
+ ```yaml
195
+ steps:
196
+ build:
197
+ type: command
198
+ exec: "npm run build"
199
+
200
+ # Working directory
201
+ cwd: "{{ outputs.checkout.path }}"
202
+
203
+ # Environment variables
204
+ env:
205
+ NODE_ENV: production
206
+ API_KEY: "{{ env.API_KEY }}"
207
+
208
+ # Output format
209
+ output_format: json # text (default), json
210
+
211
+ # Output schema for JSON
212
+ schema:
213
+ type: object
214
+ properties:
215
+ success: { type: boolean }
216
+ errors: { type: array }
217
+
218
+ multi-command:
219
+ type: command
220
+ exec: |
221
+ npm ci
222
+ npm run lint
223
+ npm test
224
+ ```
225
+
226
+ ### 4. Human Input Check (`type: human-input`)
227
+
228
+ Pause for user input.
229
+
230
+ ```yaml
231
+ steps:
232
+ get-input:
233
+ type: human-input
234
+ prompt: |
235
+ What would you like to accomplish?
236
+ Be specific about constraints and requirements.
237
+
238
+ placeholder: "Enter your task description..."
239
+ multiline: true # Allow multi-line input
240
+ allow_empty: false # Require input
241
+ default: "yes" # Default value
242
+ timeout: 300 # Timeout in seconds
243
+ ```
244
+
245
+ Output structure: `{ text: string, ts: number }`
246
+
247
+ ### 5. Log Check (`type: log`)
248
+
249
+ Output messages to the console/log.
250
+
251
+ ```yaml
252
+ steps:
253
+ finish:
254
+ type: log
255
+ depends_on: [process]
256
+ message: |
257
+ Processing complete!
258
+
259
+ Results:
260
+ {% for item in outputs['process'].results %}
261
+ - {{ item.name }}: {{ item.status }}
262
+ {% endfor %}
263
+
264
+ level: info # info, warn, error, debug
265
+ include_pr_context: false
266
+ include_dependencies: false
267
+ include_metadata: false
268
+ ```
269
+
270
+ ### 6. Script Check (`type: script`)
271
+
272
+ Execute JavaScript code.
273
+
274
+ ```yaml
275
+ steps:
276
+ transform:
277
+ type: script
278
+ content: |
279
+ const input = outputs['previous-step'];
280
+ const filtered = input.items.filter(i => i.valid);
281
+ return {
282
+ total: input.items.length,
283
+ valid: filtered.length,
284
+ items: filtered
285
+ };
286
+
287
+ # Schema for output validation
288
+ schema:
289
+ type: object
290
+ required: [total, valid, items]
291
+ ```
292
+
293
+ ### 7. GitHub Check (`type: github`)
294
+
295
+ Perform GitHub API operations.
296
+
297
+ ```yaml
298
+ steps:
299
+ add-labels:
300
+ type: github
301
+ criticality: external
302
+ depends_on: [analyze]
303
+
304
+ assume:
305
+ - "(outputs['analyze']?.labels?.length ?? 0) > 0"
306
+
307
+ op: labels.add
308
+ values:
309
+ - "{{ outputs['analyze'].labels | json }}"
310
+
311
+ create-comment:
312
+ type: github
313
+ op: comment.create
314
+ values:
315
+ body: |
316
+ ## Analysis Complete
317
+ {{ outputs['analyze'].summary }}
318
+ ```
319
+
320
+ Available operations:
321
+ - `labels.add`, `labels.remove`, `labels.set`
322
+ - `comment.create`, `comment.update`
323
+ - `review.create`, `review.approve`, `review.request_changes`
324
+ - `status.create`
325
+
326
+ ### 8. Memory Check (`type: memory`)
327
+
328
+ Store and retrieve state across steps.
329
+
330
+ ```yaml
331
+ steps:
332
+ store:
333
+ type: memory
334
+ operation: set
335
+ key: "analysis_result"
336
+ value: "{{ outputs['analyze'] | json }}"
337
+ namespace: "my-workflow"
338
+
339
+ retrieve:
340
+ type: memory
341
+ operation: get
342
+ key: "analysis_result"
343
+ namespace: "my-workflow"
344
+
345
+ increment:
346
+ type: memory
347
+ operation: increment
348
+ key: "attempt_count"
349
+ value: 1
350
+ namespace: "retry-loop"
351
+ ```
352
+
353
+ ### 9. Workflow Check (`type: workflow`)
354
+
355
+ Call another workflow as a step.
356
+
357
+ ```yaml
358
+ steps:
359
+ security-scan:
360
+ type: workflow
361
+ workflow: security-scan # Workflow ID
362
+ args:
363
+ severity_threshold: high
364
+ scan_dependencies: true
365
+
366
+ output_mapping:
367
+ vulnerabilities: scan_results
368
+ ```
369
+
370
+ ### 10. Git Checkout Check (`type: git-checkout`)
371
+
372
+ Checkout code from a repository.
373
+
374
+ ```yaml
375
+ steps:
376
+ checkout:
377
+ type: git-checkout
378
+ repository: owner/repo # GitHub repository
379
+ ref: "{{ pr.head }}" # Branch, tag, or commit
380
+
381
+ # Optional configuration
382
+ fetch_depth: 1 # Shallow clone
383
+ fetch_tags: false
384
+ submodules: false # true, false, or 'recursive'
385
+ working_directory: /tmp/checkout
386
+ ```
387
+
388
+ Output: `{ success, path, ref, commit, repository }`
389
+
390
+ ### 11. HTTP Checks
391
+
392
+ #### HTTP Client (`type: http_client`)
393
+
394
+ Make HTTP requests.
395
+
396
+ ```yaml
397
+ steps:
398
+ fetch-data:
399
+ type: http_client
400
+ url: "https://api.example.com/data"
401
+ method: POST
402
+ headers:
403
+ Authorization: "Bearer {{ env.API_TOKEN }}"
404
+ Content-Type: application/json
405
+ body: |
406
+ { "query": "{{ outputs['input'].query }}" }
407
+
408
+ schema:
409
+ type: object
410
+ properties:
411
+ data: { type: array }
412
+ ```
413
+
414
+ #### HTTP Input (`type: http_input`)
415
+
416
+ Receive data via webhook.
417
+
418
+ ```yaml
419
+ steps:
420
+ webhook-receiver:
421
+ type: http_input
422
+ path: /webhook/data
423
+ method: POST
424
+ ```
425
+
426
+ ### 12. Noop Check (`type: noop`)
427
+
428
+ A pass-through step for orchestration.
429
+
430
+ ```yaml
431
+ steps:
432
+ checkpoint:
433
+ type: noop
434
+ depends_on: [step-a, step-b, step-c]
435
+ # All dependencies must complete before dependents run
436
+ ```
437
+
438
+ ---
439
+
440
+ ## Configuration Patterns
441
+
442
+ ### Dependencies
443
+
444
+ Control execution order with `depends_on`:
445
+
446
+ ```yaml
447
+ steps:
448
+ first:
449
+ type: command
450
+ exec: "echo first"
451
+
452
+ second:
453
+ type: command
454
+ depends_on: [first] # Runs after 'first'
455
+ exec: "echo second"
456
+
457
+ parallel-a:
458
+ type: command
459
+ depends_on: [second]
460
+ exec: "echo parallel-a"
461
+
462
+ parallel-b:
463
+ type: command
464
+ depends_on: [second] # Runs in parallel with parallel-a
465
+ exec: "echo parallel-b"
466
+
467
+ final:
468
+ type: command
469
+ depends_on: [parallel-a, parallel-b] # Waits for both
470
+ exec: "echo final"
471
+ ```
472
+
473
+ ### Conditions (`if`)
474
+
475
+ Skip steps conditionally:
476
+
477
+ ```yaml
478
+ steps:
479
+ conditional:
480
+ type: command
481
+ depends_on: [check]
482
+ if: "outputs['check']?.should_run === true"
483
+ exec: "echo running"
484
+ ```
485
+
486
+ ### Guards (`assume`)
487
+
488
+ Assert preconditions before execution:
489
+
490
+ ```yaml
491
+ steps:
492
+ process:
493
+ type: command
494
+ depends_on: [fetch]
495
+ assume:
496
+ - "outputs['fetch']?.data != null"
497
+ - "(outputs['fetch']?.data?.length ?? 0) > 0"
498
+ exec: "process-data"
499
+ ```
500
+
501
+ ### Contracts (`guarantee`, `schema`)
502
+
503
+ Validate output:
504
+
505
+ ```yaml
506
+ steps:
507
+ analyze:
508
+ type: ai
509
+ prompt: "Analyze code"
510
+
511
+ # Schema validation
512
+ schema:
513
+ type: object
514
+ required: [issues]
515
+ properties:
516
+ issues: { type: array }
517
+
518
+ # Post-execution guarantee
519
+ guarantee: "output.issues != null && Array.isArray(output.issues)"
520
+ ```
521
+
522
+ ### Failure Conditions (`fail_if`)
523
+
524
+ Mark step as failed based on output:
525
+
526
+ ```yaml
527
+ steps:
528
+ validate:
529
+ type: command
530
+ exec: "./validate.sh"
531
+ fail_if: "output.code !== 0"
532
+
533
+ ai-check:
534
+ type: ai
535
+ prompt: "Check for issues"
536
+ fail_if: "output.issues?.some(i => i.severity === 'critical')"
537
+ ```
538
+
539
+ ### Routing (`on_success`, `on_fail`, `goto`)
540
+
541
+ Control flow after step completion:
542
+
543
+ ```yaml
544
+ steps:
545
+ validate:
546
+ type: command
547
+ exec: "npm test"
548
+ fail_if: "output.code !== 0"
549
+
550
+ on_fail:
551
+ run: [fix-issues] # Run remediation step
552
+ goto: validate # Then retry (ancestor only)
553
+ retry:
554
+ max: 2
555
+ backoff:
556
+ mode: exponential
557
+ delay_ms: 1000
558
+
559
+ on_success:
560
+ goto: finalize
561
+
562
+ fix-issues:
563
+ type: claude-code
564
+ prompt: "Fix the test failures"
565
+ claude_code:
566
+ allowedTools: ['Read', 'Edit']
567
+
568
+ finalize:
569
+ type: log
570
+ message: "All tests pass!"
571
+ ```
572
+
573
+ ### forEach (Fan-Out)
574
+
575
+ Process arrays in parallel:
576
+
577
+ ```yaml
578
+ steps:
579
+ extract-items:
580
+ type: ai
581
+ forEach: true # Output is treated as array
582
+ prompt: "Extract items from: {{ pr.body }}"
583
+ schema:
584
+ type: array
585
+ items:
586
+ type: object
587
+ properties:
588
+ id: { type: string }
589
+ task: { type: string }
590
+
591
+ process-item:
592
+ type: command
593
+ depends_on: [extract-items]
594
+ fanout: map # Run once per item
595
+ exec: "process {{ outputs['extract-items'].task }}"
596
+
597
+ aggregate:
598
+ type: script
599
+ depends_on: [process-item]
600
+ fanout: reduce # Run once with all results
601
+ content: |
602
+ const results = outputs_history['process-item'] || [];
603
+ return { total: results.length };
604
+ ```
605
+
606
+ ### AI Session Reuse
607
+
608
+ Continue AI conversations across steps:
609
+
610
+ ```yaml
611
+ steps:
612
+ initial-analysis:
613
+ type: ai
614
+ prompt: "Analyze the code structure"
615
+
616
+ follow-up:
617
+ type: ai
618
+ depends_on: [initial-analysis]
619
+ reuse_ai_session: initial-analysis
620
+ session_mode: clone # or 'continue'
621
+ prompt: "Now look for security issues in what we discussed"
622
+ ```
623
+
624
+ ---
625
+
626
+ ## Testing DSL
627
+
628
+ ### Test File Structure
629
+
630
+ Tests can be inline in the workflow or in a separate file:
631
+
632
+ ```yaml
633
+ # workflow-name.tests.yaml
634
+ version: "1.0"
635
+ extends: "./workflow-name.yaml"
636
+
637
+ tests:
638
+ defaults:
639
+ strict: true # Every executed step must be asserted
640
+ ai_provider: mock # Use mock AI provider
641
+ prompt_max_chars: 16000 # Truncate captured prompts
642
+ tags: "fast" # Only run steps with these tags
643
+ exclude_tags: "slow" # Skip steps with these tags
644
+
645
+ cases:
646
+ - name: basic-flow
647
+ description: Tests the happy path
648
+ event: manual # or pr_opened, pr_updated, etc.
649
+ fixture: local.minimal # Built-in or custom fixture
650
+
651
+ mocks:
652
+ step-one: "mock response"
653
+ step-two:
654
+ field: "value"
655
+ items: [1, 2, 3]
656
+ # Array mocks for loops
657
+ step-three[]:
658
+ - { attempt: 1, status: "fail" }
659
+ - { attempt: 2, status: "pass" }
660
+
661
+ expect:
662
+ calls:
663
+ - step: step-one
664
+ exactly: 1
665
+ - step: step-two
666
+ at_least: 1
667
+ at_most: 3
668
+
669
+ no_calls:
670
+ - step: should-not-run
671
+
672
+ prompts:
673
+ - step: step-one
674
+ contains:
675
+ - "expected text"
676
+ not_contains:
677
+ - "unwanted text"
678
+
679
+ outputs:
680
+ - step: step-two
681
+ path: items.length
682
+ equals: 3
683
+ - step: step-two
684
+ path: status
685
+ matches: "^(pass|success)$"
686
+ ```
687
+
688
+ ### Fixtures
689
+
690
+ Built-in fixtures:
691
+ - `gh.pr_open.minimal` - Minimal PR opened event
692
+ - `gh.pr_sync.minimal` - Minimal PR sync event
693
+ - `gh.issue_open.minimal` - Minimal issue opened event
694
+ - `gh.issue_comment.standard` - Standard issue comment
695
+ - `local.minimal` - Minimal local/manual fixture
696
+
697
+ Custom fixtures:
698
+ ```yaml
699
+ tests:
700
+ fixtures:
701
+ - name: my-fixture
702
+ extends: gh.pr_open.minimal
703
+ overrides:
704
+ pr:
705
+ title: "Custom PR Title"
706
+ labels: ["bug", "urgent"]
707
+ ```
708
+
709
+ ### Mock Types
710
+
711
+ ```yaml
712
+ mocks:
713
+ # Simple string mock (for human-input or command stdout)
714
+ get-input: "user input text"
715
+
716
+ # JSON object mock (for AI with schema)
717
+ analyze:
718
+ issues: []
719
+ summary: "All good"
720
+
721
+ # Array mock for forEach or loops
722
+ extract[]:
723
+ - { id: 1, name: "item1" }
724
+ - { id: 2, name: "item2" }
725
+
726
+ # Command mock
727
+ build:
728
+ stdout: '{"success": true}'
729
+ stderr: ""
730
+ exit_code: 0
731
+ ```
732
+
733
+ ### Expect Assertions
734
+
735
+ ```yaml
736
+ expect:
737
+ # Call count assertions
738
+ calls:
739
+ - step: my-step
740
+ exactly: 1 # Exactly N times
741
+ - step: retry-step
742
+ at_least: 1 # At least N times
743
+ at_most: 5 # At most N times
744
+
745
+ # Negative assertions
746
+ no_calls:
747
+ - step: should-skip
748
+
749
+ # Prompt assertions
750
+ prompts:
751
+ - step: ai-step
752
+ index: last # first, last, or number
753
+ contains: ["keyword"]
754
+ not_contains: ["bad"]
755
+ matches: "pattern.*"
756
+
757
+ # Output assertions
758
+ outputs:
759
+ - step: process
760
+ path: result.status # Dot notation path
761
+ equals: "success"
762
+ - step: process
763
+ path: items
764
+ contains_unordered: ["a", "b"]
765
+ - step: process
766
+ where: { path: type, equals: "important" }
767
+ path: value
768
+ matches: "\\d+"
769
+
770
+ # Failure assertions
771
+ fail:
772
+ message_contains: "expected error"
773
+ ```
774
+
775
+ ### Flow Tests (Multi-Stage)
776
+
777
+ Test sequences of events:
778
+
779
+ ```yaml
780
+ tests:
781
+ cases:
782
+ - name: multi-event-flow
783
+ flow:
784
+ - name: pr-opened
785
+ event: pr_opened
786
+ fixture: gh.pr_open.minimal
787
+ mocks:
788
+ overview: { text: "Initial review" }
789
+ expect:
790
+ calls:
791
+ - step: overview
792
+ exactly: 1
793
+
794
+ - name: pr-updated
795
+ event: pr_updated
796
+ fixture: gh.pr_sync.minimal
797
+ mocks:
798
+ overview: { text: "Updated review" }
799
+ expect:
800
+ calls:
801
+ - step: overview
802
+ exactly: 1
803
+ ```
804
+
805
+ ---
806
+
807
+ ## Style Guide
808
+
809
+ ### Key Principles
810
+
811
+ 1. **One step, one responsibility** - Keep steps focused and composable
812
+ 2. **Declare intent before mechanics** - Readers should understand what/when before how
813
+ 3. **Guard and contract every important step** - Use `assume` and `schema`/`guarantee`
814
+ 4. **Avoid hidden control flow** - Prefer declarative routing over imperative logic
815
+
816
+ ### Recommended Key Order
817
+
818
+ For each step, use this order:
819
+
820
+ ```yaml
821
+ my-step:
822
+ # 1. Identity & Intent
823
+ type: ai
824
+ criticality: external # external, internal, policy, info
825
+ group: analysis
826
+ tags: [security, slow]
827
+ description: Analyzes code for security issues
828
+
829
+ # 2. Triggers & Dependencies
830
+ on: [pr_opened, pr_updated]
831
+ depends_on: [overview]
832
+
833
+ # 3. Preconditions (Guards)
834
+ assume:
835
+ - "outputs['overview']?.text != null"
836
+ if: "outputs['overview']?.shouldAnalyze === true"
837
+
838
+ # 4. Provider Configuration
839
+ prompt: |
840
+ Analyze for security issues...
841
+ ai:
842
+ provider: anthropic
843
+ model: claude-3-opus-20240229
844
+
845
+ # 5. Contracts (Post-Exec)
846
+ schema: code-review
847
+ guarantee: "output.issues != null"
848
+
849
+ # 6. Failure Policies
850
+ fail_if: "output.issues?.some(i => i.severity === 'critical')"
851
+ continue_on_failure: false
852
+
853
+ # 7. Routing & Transitions
854
+ on_success:
855
+ goto: next-step
856
+ on_fail:
857
+ run: [fix-step]
858
+ goto: my-step
859
+
860
+ # 8. Runtime Controls
861
+ timeout: 120
862
+ retries: 2
863
+ ```
864
+
865
+ ### Criticality Levels
866
+
867
+ - **`external`**: Side effects outside repo/CI (GitHub ops, webhooks)
868
+ - Requires: `assume` or `if` precondition
869
+ - Requires: `schema` or `guarantee` for outputs
870
+
871
+ - **`internal`**: Orchestration/state within CI
872
+ - Same requirements as `external`
873
+
874
+ - **`policy`**: Evaluative checks (security, quality)
875
+ - Guards/contracts optional
876
+
877
+ - **`info`**: Purely informational, never gates dependents
878
+
879
+ ### Do's and Don'ts
880
+
881
+ **Do:**
882
+ - Declare `criticality` and follow guard/contract rules
883
+ - Keep expressions short and defensive: `outputs?.x?.length ?? 0`
884
+ - Add `schema` whenever output shape matters
885
+ - Use meaningful step names
886
+ - Include tests for all workflows
887
+
888
+ **Don't:**
889
+ - Hide control flow in templates or long JS snippets
890
+ - Mix unrelated responsibilities in a single step
891
+ - Depend on outputs you didn't guard
892
+ - Use magic numbers without explanation
893
+ - Create workflows without tests
894
+
895
+ ---
896
+
897
+ ## Example Patterns
898
+
899
+ ### Human-in-the-Loop with Refinement
900
+
901
+ ```yaml
902
+ steps:
903
+ get-task:
904
+ type: human-input
905
+ prompt: "Describe what you want to accomplish"
906
+ multiline: true
907
+ allow_empty: false
908
+
909
+ refine:
910
+ type: ai
911
+ depends_on: [get-task]
912
+ ai:
913
+ disableTools: true
914
+ schema:
915
+ type: object
916
+ properties:
917
+ refined: { type: boolean }
918
+ text: { type: string }
919
+ required: [refined, text]
920
+ prompt: |
921
+ Refine this task into clear, actionable requirements:
922
+ {{ outputs['get-task'].text }}
923
+
924
+ If clarification is needed, set refined=false and ask in text.
925
+ If complete, set refined=true with the final specification.
926
+
927
+ fail_if: "output.refined !== true"
928
+ on_fail:
929
+ goto: get-task
930
+ ```
931
+
932
+ ### Validation Loop with Retry
933
+
934
+ ```yaml
935
+ routing:
936
+ max_loops: 5
937
+
938
+ steps:
939
+ generate:
940
+ type: ai
941
+ prompt: "Generate code for: {{ inputs.task }}"
942
+
943
+ validate:
944
+ type: command
945
+ depends_on: [generate]
946
+ exec: "npm run lint && npm test"
947
+ fail_if: "output.code !== 0"
948
+ on_fail:
949
+ run: [fix]
950
+ on_success:
951
+ goto: complete
952
+
953
+ fix:
954
+ type: claude-code
955
+ depends_on: [validate]
956
+ if: "outputs['validate']?.code !== 0"
957
+ prompt: |
958
+ Fix these errors:
959
+ {{ outputs['validate'].stderr }}
960
+ claude_code:
961
+ allowedTools: ['Read', 'Edit']
962
+ maxTurns: 5
963
+ on_success:
964
+ goto: validate
965
+
966
+ complete:
967
+ type: log
968
+ depends_on: [validate]
969
+ message: "Validation passed!"
970
+ ```
971
+
972
+ ### Multi-AI Review Pipeline
973
+
974
+ ```yaml
975
+ steps:
976
+ overview:
977
+ type: ai
978
+ on: [pr_opened, pr_updated]
979
+ prompt: "Provide PR overview"
980
+ schema: overview
981
+
982
+ security:
983
+ type: ai
984
+ depends_on: [overview]
985
+ prompt: "Analyze for security issues"
986
+ schema: code-review
987
+
988
+ performance:
989
+ type: ai
990
+ depends_on: [overview]
991
+ prompt: "Analyze for performance issues"
992
+ schema: code-review
993
+
994
+ aggregate:
995
+ type: script
996
+ depends_on: [security, performance]
997
+ content: |
998
+ const all = [
999
+ ...(outputs['security']?.issues || []),
1000
+ ...(outputs['performance']?.issues || [])
1001
+ ];
1002
+ return {
1003
+ issues: all,
1004
+ hasErrors: all.some(i => i.severity === 'critical' || i.severity === 'error')
1005
+ };
1006
+ ```
1007
+
1008
+ ### GitHub Integration
1009
+
1010
+ ```yaml
1011
+ steps:
1012
+ analyze:
1013
+ type: ai
1014
+ on: [pr_opened]
1015
+ prompt: "Analyze and suggest labels"
1016
+ schema:
1017
+ type: object
1018
+ properties:
1019
+ labels: { type: array, items: { type: string } }
1020
+ effort: { type: integer, minimum: 1, maximum: 5 }
1021
+
1022
+ apply-labels:
1023
+ type: github
1024
+ criticality: external
1025
+ depends_on: [analyze]
1026
+ assume:
1027
+ - "(outputs['analyze']?.labels?.length ?? 0) > 0"
1028
+ op: labels.add
1029
+ values:
1030
+ - "{{ outputs['analyze'].labels | json }}"
1031
+ - "effort:{{ outputs['analyze'].effort }}"
1032
+ ```
1033
+
1034
+ ---
1035
+
1036
+ ## Common Pitfalls
1037
+
1038
+ ### 1. Missing Dependencies
1039
+
1040
+ **Wrong:**
1041
+ ```yaml
1042
+ steps:
1043
+ process:
1044
+ type: command
1045
+ exec: "process {{ outputs['fetch'].data }}" # fetch not declared as dependency
1046
+ ```
1047
+
1048
+ **Right:**
1049
+ ```yaml
1050
+ steps:
1051
+ process:
1052
+ type: command
1053
+ depends_on: [fetch]
1054
+ exec: "process {{ outputs['fetch'].data }}"
1055
+ ```
1056
+
1057
+ ### 2. Infinite Routing Loops
1058
+
1059
+ **Wrong:**
1060
+ ```yaml
1061
+ steps:
1062
+ step-a:
1063
+ on_fail:
1064
+ goto: step-b
1065
+ step-b:
1066
+ depends_on: [step-a]
1067
+ on_fail:
1068
+ goto: step-a # Infinite loop!
1069
+ ```
1070
+
1071
+ **Right:**
1072
+ ```yaml
1073
+ routing:
1074
+ max_loops: 3 # Limit iterations
1075
+
1076
+ steps:
1077
+ step-a:
1078
+ on_fail:
1079
+ goto: step-b
1080
+ retry:
1081
+ max: 2 # Limit retries
1082
+ ```
1083
+
1084
+ ### 3. Unguarded External Operations
1085
+
1086
+ **Wrong:**
1087
+ ```yaml
1088
+ steps:
1089
+ add-labels:
1090
+ type: github
1091
+ op: labels.add
1092
+ values:
1093
+ - "{{ outputs['analyze'].labels }}" # May be null!
1094
+ ```
1095
+
1096
+ **Right:**
1097
+ ```yaml
1098
+ steps:
1099
+ add-labels:
1100
+ type: github
1101
+ criticality: external
1102
+ depends_on: [analyze]
1103
+ assume:
1104
+ - "(outputs['analyze']?.labels?.length ?? 0) > 0"
1105
+ op: labels.add
1106
+ values:
1107
+ - "{{ outputs['analyze'].labels | json }}"
1108
+ ```
1109
+
1110
+ ### 4. Tests Without Strict Mode
1111
+
1112
+ **Wrong:**
1113
+ ```yaml
1114
+ tests:
1115
+ cases:
1116
+ - name: test
1117
+ mocks:
1118
+ step-one: "value"
1119
+ expect:
1120
+ outputs:
1121
+ - step: step-one
1122
+ path: text
1123
+ equals: "value"
1124
+ # Missing call assertions - unexecuted steps go unnoticed
1125
+ ```
1126
+
1127
+ **Right:**
1128
+ ```yaml
1129
+ tests:
1130
+ defaults:
1131
+ strict: true # Require call assertions for all executed steps
1132
+ cases:
1133
+ - name: test
1134
+ mocks:
1135
+ step-one: "value"
1136
+ expect:
1137
+ calls:
1138
+ - step: step-one
1139
+ exactly: 1
1140
+ outputs:
1141
+ - step: step-one
1142
+ path: text
1143
+ equals: "value"
1144
+ ```
1145
+
1146
+ ### 5. Forgetting forEach Fanout
1147
+
1148
+ **Wrong:**
1149
+ ```yaml
1150
+ steps:
1151
+ extract:
1152
+ type: ai
1153
+ forEach: true
1154
+ prompt: "Extract items"
1155
+
1156
+ process:
1157
+ depends_on: [extract]
1158
+ # Runs once with first item only!
1159
+ ```
1160
+
1161
+ **Right:**
1162
+ ```yaml
1163
+ steps:
1164
+ extract:
1165
+ type: ai
1166
+ forEach: true
1167
+ prompt: "Extract items"
1168
+
1169
+ process:
1170
+ depends_on: [extract]
1171
+ fanout: map # Runs for each item
1172
+ ```
1173
+
1174
+ ### 6. Hardcoded Values in Tests
1175
+
1176
+ **Wrong:**
1177
+ ```yaml
1178
+ expect:
1179
+ outputs:
1180
+ - step: calculate
1181
+ path: result
1182
+ equals: 42 # Magic number - why 42?
1183
+ ```
1184
+
1185
+ **Right:**
1186
+ ```yaml
1187
+ # Use meaningful values that relate to the mock inputs
1188
+ mocks:
1189
+ input: { value: 6 }
1190
+ multiplier: { value: 7 }
1191
+
1192
+ expect:
1193
+ outputs:
1194
+ - step: calculate
1195
+ path: result
1196
+ equals: 42 # 6 * 7 = 42, derivable from inputs
1197
+ ```
1198
+
1199
+ ---
1200
+
1201
+ ## Quick Reference
1202
+
1203
+ ### Template Variables
1204
+
1205
+ Available in prompts and Liquid templates:
1206
+
1207
+ | Variable | Description |
1208
+ |----------|-------------|
1209
+ | `pr` | PR metadata (title, body, author, labels, etc.) |
1210
+ | `files` | Changed files list |
1211
+ | `outputs` | Map of dependency outputs |
1212
+ | `outputs['step-name']` | Specific step output |
1213
+ | `outputs_history['step-name']` | All historical outputs for step |
1214
+ | `outputs_raw['step-name']` | Raw/aggregate output |
1215
+ | `env` | Environment variables |
1216
+ | `event` | Current event details |
1217
+ | `inputs` | Workflow input parameters |
1218
+
1219
+ ### Liquid Filters
1220
+
1221
+ | Filter | Example | Description |
1222
+ |--------|---------|-------------|
1223
+ | `json` | `{{ data \| json }}` | JSON encode |
1224
+ | `default` | `{{ x \| default: 'fallback' }}` | Default value |
1225
+ | `size` | `{{ arr \| size }}` | Array/string length |
1226
+ | `first` | `{{ arr \| first }}` | First element |
1227
+ | `last` | `{{ arr \| last }}` | Last element |
1228
+ | `join` | `{{ arr \| join: ', ' }}` | Join array |
1229
+ | `split` | `{{ str \| split: ',' }}` | Split string |
1230
+ | `upcase` | `{{ str \| upcase }}` | Uppercase |
1231
+ | `downcase` | `{{ str \| downcase }}` | Lowercase |
1232
+
1233
+ ### JS Expression Context
1234
+
1235
+ Available in `if`, `fail_if`, `assume`, `guarantee`, `run_js`, `goto_js`:
1236
+
1237
+ | Variable | Description |
1238
+ |----------|-------------|
1239
+ | `output` | Current step's output |
1240
+ | `outputs` | Map of dependency outputs |
1241
+ | `outputs.history` | Historical outputs map |
1242
+ | `attempt` | Current attempt number |
1243
+ | `loop` | Current routing loop number |
1244
+ | `step` | Current step metadata |
1245
+ | `pr` | PR metadata |
1246
+ | `files` | Changed files |
1247
+ | `env` | Environment variables |
1248
+ | `event` | Event metadata |
1249
+ | `memory` | Memory access (get/set/increment) |
1250
+
1251
+ ### CLI Commands
1252
+
1253
+ ```bash
1254
+ # Run workflow
1255
+ visor --config workflow.yaml
1256
+
1257
+ # Run with message (for human-input)
1258
+ visor --config workflow.yaml --message "input text"
1259
+
1260
+ # Validate configuration
1261
+ visor validate --config workflow.yaml
1262
+
1263
+ # Run tests
1264
+ visor test --config workflow.tests.yaml
1265
+
1266
+ # Run specific test
1267
+ visor test --only test-name
1268
+
1269
+ # List tests
1270
+ visor test --list
1271
+
1272
+ # Validate tests only
1273
+ visor test --validate
1274
+ ```