@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.
- package/defaults/workflow-builder.tests.yaml +363 -0
- package/defaults/workflow-builder.yaml +720 -0
- package/dist/ai-review-service.d.ts.map +1 -1
- package/dist/cli-main.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/defaults/workflow-builder.tests.yaml +363 -0
- package/dist/defaults/workflow-builder.yaml +720 -0
- package/dist/docs/workflow-creation-guide.md +1274 -0
- package/dist/frontends/slack-frontend.d.ts +3 -0
- package/dist/frontends/slack-frontend.d.ts.map +1 -1
- package/dist/generated/config-schema.d.ts +14 -6
- package/dist/generated/config-schema.d.ts.map +1 -1
- package/dist/generated/config-schema.json +14 -6
- package/dist/index.js +36879 -13895
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/mcp-server.d.ts +4 -4
- package/dist/output/traces/{run-2026-01-21T12-31-10-108Z.ndjson → run-2026-01-28T13-56-32-263Z.ndjson} +84 -84
- package/dist/output/traces/run-2026-01-28T13-57-13-140Z.ndjson +1357 -0
- package/dist/providers/ai-check-provider.d.ts.map +1 -1
- package/dist/providers/check-provider.interface.d.ts +15 -1
- package/dist/providers/check-provider.interface.d.ts.map +1 -1
- package/dist/providers/command-check-provider.d.ts.map +1 -1
- package/dist/providers/git-checkout-provider.d.ts.map +1 -1
- package/dist/sdk/{check-provider-registry-534KL5HT.mjs → check-provider-registry-JMNLGIMJ.mjs} +11 -11
- package/dist/sdk/{chunk-AIVFBIS4.mjs → chunk-35NT3725.mjs} +30 -10
- package/dist/sdk/chunk-35NT3725.mjs.map +1 -0
- package/dist/sdk/{chunk-AGIZJ4UZ.mjs → chunk-3NMLT3YS.mjs} +42 -8
- package/dist/sdk/chunk-3NMLT3YS.mjs.map +1 -0
- package/dist/sdk/{chunk-AK6BVWIT.mjs → chunk-7GUAFV6L.mjs} +2 -2
- package/dist/sdk/{chunk-QY2XYPEV.mjs → chunk-CUNPH6TR.mjs} +18 -10
- package/dist/sdk/chunk-CUNPH6TR.mjs.map +1 -0
- package/dist/sdk/{chunk-HTOKWMPO.mjs → chunk-HQL734ZI.mjs} +2 -2
- package/dist/sdk/{chunk-7UK3NIIT.mjs → chunk-IHZOSIF4.mjs} +2 -2
- package/dist/sdk/{chunk-AUT26LHW.mjs → chunk-J2QWVDXK.mjs} +2 -2
- package/dist/sdk/{chunk-QR7MOMJH.mjs → chunk-J6EVEXC2.mjs} +2 -2
- package/dist/sdk/{chunk-SIWNBRTK.mjs → chunk-SWEEZ5D5.mjs} +3 -3
- package/dist/sdk/{chunk-23L3QRYX.mjs → chunk-VPEQOQ7G.mjs} +279 -70
- package/dist/sdk/chunk-VPEQOQ7G.mjs.map +1 -0
- package/dist/sdk/{command-executor-TYUV6HUS.mjs → command-executor-Q7MHJKZJ.mjs} +3 -3
- package/dist/sdk/{config-YNC2EOOT.mjs → config-MK4XTU45.mjs} +3 -3
- package/dist/sdk/{failure-condition-evaluator-YGTF2GHG.mjs → failure-condition-evaluator-HB35XRLZ.mjs} +4 -4
- package/dist/sdk/{github-frontend-SIAEOCON.mjs → github-frontend-6Q4BISZX.mjs} +4 -4
- package/dist/sdk/{host-DXUYTNMU.mjs → host-P5NQICP7.mjs} +3 -3
- package/dist/sdk/{liquid-extensions-PKWCKK7E.mjs → liquid-extensions-DFDEBMUI.mjs} +4 -4
- package/dist/sdk/{memory-store-XGBB7LX7.mjs → memory-store-RW5N2NGJ.mjs} +3 -3
- package/dist/sdk/{prompt-state-YRJY6QAL.mjs → prompt-state-EZYOUG75.mjs} +3 -3
- package/dist/sdk/{renderer-schema-LPKN5UJS.mjs → renderer-schema-CKFB5NDB.mjs} +2 -2
- package/dist/sdk/{routing-6N45MJ4F.mjs → routing-KZ345OFG.mjs} +5 -5
- package/dist/sdk/sdk.d.mts +19 -1
- package/dist/sdk/sdk.d.ts +19 -1
- package/dist/sdk/sdk.js +421 -75
- package/dist/sdk/sdk.js.map +1 -1
- package/dist/sdk/sdk.mjs +35 -18
- package/dist/sdk/sdk.mjs.map +1 -1
- package/dist/sdk/{slack-frontend-BVKW3GD5.mjs → slack-frontend-J442FJWZ.mjs} +61 -3
- package/dist/sdk/slack-frontend-J442FJWZ.mjs.map +1 -0
- package/dist/sdk/{workflow-registry-R6KSACFR.mjs → workflow-registry-6LZKCWHP.mjs} +3 -3
- package/dist/state-machine/context/build-engine-context.d.ts.map +1 -1
- package/dist/state-machine/dispatch/history-snapshot.d.ts.map +1 -1
- package/dist/state-machine/runner.d.ts.map +1 -1
- package/dist/state-machine/states/level-dispatch.d.ts.map +1 -1
- package/dist/state-machine/states/routing.d.ts.map +1 -1
- package/dist/state-machine/states/wave-planning.d.ts.map +1 -1
- package/dist/ter-u14b.json +17826 -0
- package/dist/ter-u14n.json +17826 -0
- package/dist/test-runner/index.d.ts.map +1 -1
- package/dist/traces/{run-2026-01-21T12-31-10-108Z.ndjson → run-2026-01-28T13-56-32-263Z.ndjson} +84 -84
- package/dist/traces/run-2026-01-28T13-57-13-140Z.ndjson +1357 -0
- package/dist/tui.d.ts +51 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/types/cli.d.ts +10 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/engine.d.ts +3 -0
- package/dist/types/engine.d.ts.map +1 -1
- package/dist/usr/fonts/AUTHORS +1 -0
- package/dist/usr/fonts/LICENSE +94 -0
- package/dist/usr/fonts/README +340 -0
- package/dist/usr/fonts/ter-u14b.json +17826 -0
- package/dist/usr/fonts/ter-u14n.json +17826 -0
- package/dist/usr/linux +0 -0
- package/dist/usr/windows-ansi +0 -0
- package/dist/usr/xterm +0 -0
- package/dist/usr/xterm-256color +0 -0
- package/dist/usr/xterm.termcap +243 -0
- package/dist/usr/xterm.terminfo +1977 -0
- package/dist/utils/workspace-manager.d.ts +2 -0
- package/dist/utils/workspace-manager.d.ts.map +1 -1
- package/dist/utils/worktree-manager.d.ts +5 -0
- package/dist/utils/worktree-manager.d.ts.map +1 -1
- package/dist/xterm +0 -0
- package/dist/xterm.termcap +243 -0
- package/package.json +9 -7
- package/dist/output/traces/run-2026-01-21T12-32-04-510Z.ndjson +0 -1067
- package/dist/sdk/chunk-23L3QRYX.mjs.map +0 -1
- package/dist/sdk/chunk-AGIZJ4UZ.mjs.map +0 -1
- package/dist/sdk/chunk-AIVFBIS4.mjs.map +0 -1
- package/dist/sdk/chunk-QY2XYPEV.mjs.map +0 -1
- package/dist/sdk/slack-frontend-BVKW3GD5.mjs.map +0 -1
- package/dist/traces/run-2026-01-21T12-32-04-510Z.ndjson +0 -1067
- /package/dist/sdk/{check-provider-registry-534KL5HT.mjs.map → check-provider-registry-JMNLGIMJ.mjs.map} +0 -0
- /package/dist/sdk/{chunk-AK6BVWIT.mjs.map → chunk-7GUAFV6L.mjs.map} +0 -0
- /package/dist/sdk/{chunk-HTOKWMPO.mjs.map → chunk-HQL734ZI.mjs.map} +0 -0
- /package/dist/sdk/{chunk-7UK3NIIT.mjs.map → chunk-IHZOSIF4.mjs.map} +0 -0
- /package/dist/sdk/{chunk-AUT26LHW.mjs.map → chunk-J2QWVDXK.mjs.map} +0 -0
- /package/dist/sdk/{chunk-QR7MOMJH.mjs.map → chunk-J6EVEXC2.mjs.map} +0 -0
- /package/dist/sdk/{chunk-SIWNBRTK.mjs.map → chunk-SWEEZ5D5.mjs.map} +0 -0
- /package/dist/sdk/{command-executor-TYUV6HUS.mjs.map → command-executor-Q7MHJKZJ.mjs.map} +0 -0
- /package/dist/sdk/{config-YNC2EOOT.mjs.map → config-MK4XTU45.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-YGTF2GHG.mjs.map → failure-condition-evaluator-HB35XRLZ.mjs.map} +0 -0
- /package/dist/sdk/{github-frontend-SIAEOCON.mjs.map → github-frontend-6Q4BISZX.mjs.map} +0 -0
- /package/dist/sdk/{host-DXUYTNMU.mjs.map → host-P5NQICP7.mjs.map} +0 -0
- /package/dist/sdk/{liquid-extensions-PKWCKK7E.mjs.map → liquid-extensions-DFDEBMUI.mjs.map} +0 -0
- /package/dist/sdk/{memory-store-XGBB7LX7.mjs.map → memory-store-RW5N2NGJ.mjs.map} +0 -0
- /package/dist/sdk/{prompt-state-YRJY6QAL.mjs.map → prompt-state-EZYOUG75.mjs.map} +0 -0
- /package/dist/sdk/{renderer-schema-LPKN5UJS.mjs.map → renderer-schema-CKFB5NDB.mjs.map} +0 -0
- /package/dist/sdk/{routing-6N45MJ4F.mjs.map → routing-KZ345OFG.mjs.map} +0 -0
- /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
|
+
```
|