@harms-haus/pi-workflows 1.0.0
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/LICENSE +21 -0
- package/README.md +113 -0
- package/docs/architecture.md +318 -0
- package/docs/configuration-reference.md +427 -0
- package/docs/contributing.md +132 -0
- package/docs/examples.md +1242 -0
- package/docs/hook-lifecycle.md +380 -0
- package/docs/state-management.md +534 -0
- package/docs/subworkflows.md +428 -0
- package/docs/template-variables.md +383 -0
- package/docs/testing.md +479 -0
- package/package.json +69 -0
- package/skills/workflow-generation/SKILL.md +272 -0
- package/src/TimerManager.ts +67 -0
- package/src/command.ts +199 -0
- package/src/config/index.ts +11 -0
- package/src/config/loading-parse.ts +205 -0
- package/src/config/loading-phases.ts +78 -0
- package/src/config/loading-resolve.ts +82 -0
- package/src/config/loading.ts +202 -0
- package/src/config/templates.ts +25 -0
- package/src/config/validation.ts +258 -0
- package/src/hooks.ts +265 -0
- package/src/index.ts +98 -0
- package/src/prompts.ts +141 -0
- package/src/renderers.ts +46 -0
- package/src/state.ts +426 -0
- package/src/tool.ts +364 -0
- package/src/types.ts +211 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# Subworkflows
|
|
2
|
+
|
|
3
|
+
## What Are Subworkflows
|
|
4
|
+
|
|
5
|
+
A **subworkflow** is a workflow definition referenced as a phase entry inside another workflow's `phases` array. The entire subworkflow's phase sequence runs as a single logical "phase" within the parent. When the subworkflow completes (all its phases finish), control returns to the parent's next phase.
|
|
6
|
+
|
|
7
|
+
Subworkflows enable composition: build small, reusable workflow units and combine them into larger pipelines without duplicating phase definitions.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Parent Workflow
|
|
11
|
+
├── Phase A: "Gather Requirements"
|
|
12
|
+
├── Phase B: "Code Review Cycle" ← This is actually a subworkflow
|
|
13
|
+
│ ├── Phase B1: "Static Analysis"
|
|
14
|
+
│ ├── Phase B2: "Peer Review"
|
|
15
|
+
│ └── Phase B3: "Approval Gate"
|
|
16
|
+
├── Phase C: "Deploy"
|
|
17
|
+
└── Phase D: "Verify"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Declaring a Subworkflow Reference
|
|
23
|
+
|
|
24
|
+
In `workflow.yaml`, use an **object syntax** to declare a subworkflow reference within the `phases` array:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
# my-workflow/workflow.yaml
|
|
28
|
+
name: "Release Pipeline"
|
|
29
|
+
commandName: "release"
|
|
30
|
+
initialMessage: "Starting {workflowName} for: {description}"
|
|
31
|
+
phases:
|
|
32
|
+
- build.md # concrete phase (string = filename)
|
|
33
|
+
- { subworkflow: code-review } # subworkflow reference
|
|
34
|
+
- deploy.md # concrete phase
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The `subworkflow` value must match the **directory name** of another workflow loaded from the same workflows root. During the two-pass loading process (see [Resolution and Loading](#resolution-and-loading)), the reference is replaced with a resolved link to the target workflow definition.
|
|
38
|
+
|
|
39
|
+
### In-memory representation
|
|
40
|
+
|
|
41
|
+
After loading, a subworkflow reference is represented as a [`SubworkflowReference`](../src/types.ts) object:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
interface SubworkflowReference {
|
|
45
|
+
subworkflow: true; // discriminator
|
|
46
|
+
workflowKey: string; // directory name of the target workflow
|
|
47
|
+
resolved: WorkflowDefinition | null; // null after Pass 1, populated during Pass 2 resolution
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The `resolved` field is initialized as `null` during Pass 1 loading and only populated with the target `WorkflowDefinition` during Pass 2 resolution. Code that traverses phase entries should check for `resolved === null` to detect unresolved references.
|
|
52
|
+
|
|
53
|
+
The type guard `isSubworkflowRef(entry)` distinguishes subworkflow references from plain `PhaseDefinition` objects.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Subworkflow-Only Workflows
|
|
58
|
+
|
|
59
|
+
Some workflows exist solely to be consumed as subworkflows — they should never appear in the `/workflow` slash command menu. Set `show: "workflows"` in their `workflow.yaml`:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
# _shared/code-review/workflow.yaml
|
|
63
|
+
name: "Code Review Cycle"
|
|
64
|
+
show: "workflows" # hidden from /workflow command
|
|
65
|
+
loopable: false
|
|
66
|
+
phases:
|
|
67
|
+
- static-analysis.md
|
|
68
|
+
- peer-review.md
|
|
69
|
+
- approval-gate.md
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
When `show` is `"workflows"`:
|
|
73
|
+
|
|
74
|
+
- The workflow is **excluded** from the `/workflow` command list.
|
|
75
|
+
- `commandName` and `initialMessage` are **optional** (defaults to empty string).
|
|
76
|
+
- The workflow can still be freely referenced as a subworkflow by other workflows.
|
|
77
|
+
|
|
78
|
+
When `show` is omitted or `"user"` (the default), the workflow is visible to users and `commandName`/`initialMessage` are required.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Resolution and Loading
|
|
83
|
+
|
|
84
|
+
Workflows are loaded via a **two-pass** process:
|
|
85
|
+
|
|
86
|
+
### Pass 1 — Load all directories
|
|
87
|
+
|
|
88
|
+
1. Scan the global directory (`~/.pi/agent/workflows/`) and project-local directory (`.pi/workflows/`).
|
|
89
|
+
2. Each subdirectory containing a `workflow.yaml` is loaded as a `WorkflowDefinition`.
|
|
90
|
+
3. Project definitions override global definitions with the same key (directory name).
|
|
91
|
+
4. Each definition is validated with `validateWorkflowDefinition()`. Invalid definitions are excluded.
|
|
92
|
+
5. Subworkflow references are parsed but **not yet resolved** — the `resolved` field is `null`.
|
|
93
|
+
|
|
94
|
+
### Pass 2 — Resolve references (with cascading)
|
|
95
|
+
|
|
96
|
+
After all definitions are loaded, subworkflow references are resolved in a loop that repeats until stable:
|
|
97
|
+
|
|
98
|
+
1. For each workflow containing an unresolved `SubworkflowReference`, look up `workflowKey` in the loaded definitions map.
|
|
99
|
+
2. If the target **does not exist**, the referencing workflow is **excluded** (removed from the valid set) and a warning is logged:
|
|
100
|
+
```
|
|
101
|
+
[pi-workflows] Workflow "my-pipeline" references non-existent subworkflow "missing-step". Skipping.
|
|
102
|
+
```
|
|
103
|
+
3. If the target **exists**, the `resolved` field is populated with the target `WorkflowDefinition`.
|
|
104
|
+
4. Repeat until no more exclusions occur — this handles [cascading exclusion](#cascading-exclusion).
|
|
105
|
+
|
|
106
|
+
### Load-time ordering summary
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
1. Load all workflow directories → Record<string, WorkflowDefinition>
|
|
110
|
+
2. Validate each definition → remove invalid
|
|
111
|
+
3. Detect cycles (DFS) → remove cyclic workflows
|
|
112
|
+
4. Resolve subworkflow references → populate .resolved, remove broken
|
|
113
|
+
5. Repeat step 4 until stable → handle cascading
|
|
114
|
+
6. Check for duplicate commandNames → warn (first wins)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Stack-Based Navigation
|
|
120
|
+
|
|
121
|
+
The runtime uses a **path stack** (`currentPath`) to track position within potentially nested workflows. Each element is a [`PathSegment`](../src/types.ts):
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
interface PathSegment {
|
|
125
|
+
workflowKey: string; // which workflow
|
|
126
|
+
phaseIndex: number; // which phase within that workflow
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- **Index 0** = top-level (root) workflow.
|
|
131
|
+
- **Last index** = innermost (currently active) scope.
|
|
132
|
+
- A single-element stack means a flat, non-nested workflow.
|
|
133
|
+
|
|
134
|
+
### Visualizing the stack
|
|
135
|
+
|
|
136
|
+
Consider a parent workflow with a subworkflow that itself contains a nested subworkflow:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
Parent "release" phases: [build, {subworkflow: review}, deploy]
|
|
140
|
+
└── review phases: [static, {subworkflow: security}, approval]
|
|
141
|
+
└── security phases: [scan, report]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
When the agent is executing the "scan" phase of "security":
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
currentPath stack (bottom → top):
|
|
148
|
+
|
|
149
|
+
┌─────────────────────────────────┐
|
|
150
|
+
│ { workflowKey: "release", │ ← root scope
|
|
151
|
+
│ phaseIndex: 1 } │ (at the subworkflow entry)
|
|
152
|
+
├─────────────────────────────────┤
|
|
153
|
+
│ { workflowKey: "review", │ ← middle scope
|
|
154
|
+
│ phaseIndex: 1 } │ (at the subworkflow entry)
|
|
155
|
+
├─────────────────────────────────┤
|
|
156
|
+
│ { workflowKey: "security", │ ← innermost (active)
|
|
157
|
+
│ phaseIndex: 0 } │ (at "scan" phase)
|
|
158
|
+
└─────────────────────────────────┘
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Advance cases
|
|
162
|
+
|
|
163
|
+
`advancePhase()` in [`state.ts`](../src/state.ts) handles four cases when the agent calls `workflow_step` with action `next`:
|
|
164
|
+
|
|
165
|
+
| Case | Condition | Action |
|
|
166
|
+
| ---------------------------- | -------------------------------------------------------- | ---------------------------------------------------------- |
|
|
167
|
+
| **1 — Enter subworkflow** | Current entry is a `SubworkflowReference` | **Push** new `PathSegment` onto stack with `phaseIndex: 0` |
|
|
168
|
+
| **2 — Normal advance** | Current entry is a concrete phase, not the last in scope | Increment `phaseIndex` in the top segment |
|
|
169
|
+
| **3 — Top-level done** | Last phase in root scope (`currentPath.length === 1`) | Set `active = false`, workflow is DONE |
|
|
170
|
+
| **4 — Subworkflow complete** | Last phase in a subworkflow scope | **Pop** the stack, increment parent's `phaseIndex` |
|
|
171
|
+
|
|
172
|
+
#### Case 1 diagram — entering a subworkflow
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
BEFORE: currentPath = [{ release, phaseIndex: 0 }]
|
|
176
|
+
→ current entry = "build" (concrete phase)
|
|
177
|
+
|
|
178
|
+
ADVANCE: current entry at index 0 is concrete, not last
|
|
179
|
+
→ phaseIndex++ (Case 2)
|
|
180
|
+
|
|
181
|
+
BEFORE: currentPath = [{ release, phaseIndex: 1 }]
|
|
182
|
+
→ current entry = { subworkflow: "review" }
|
|
183
|
+
|
|
184
|
+
ADVANCE: push { review, phaseIndex: 0 } (Case 1)
|
|
185
|
+
|
|
186
|
+
AFTER: currentPath = [{ release, 1 }, { review, 0 }]
|
|
187
|
+
→ now executing review's first phase
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Case 4 diagram — completing a subworkflow
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
BEFORE: currentPath = [{ release, 1 }, { review, 2 }]
|
|
194
|
+
→ review phaseIndex 2 = "approval" (last phase in review)
|
|
195
|
+
|
|
196
|
+
ADVANCE: pop { review, 2 }, increment parent to phaseIndex 2 (Case 4)
|
|
197
|
+
|
|
198
|
+
AFTER: currentPath = [{ release, 2 }]
|
|
199
|
+
→ now executing release's "deploy" phase
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Cycle Detection
|
|
205
|
+
|
|
206
|
+
Cycles in the subworkflow reference graph are detected **at load time** using iterative DFS with 3-color marking. This prevents infinite loops during execution.
|
|
207
|
+
|
|
208
|
+
### Algorithm
|
|
209
|
+
|
|
210
|
+
1. Build an adjacency list from all subworkflow references.
|
|
211
|
+
2. Mark every node `WHITE` (unvisited).
|
|
212
|
+
3. For each `WHITE` node, run iterative DFS:
|
|
213
|
+
- `GRAY` = currently being explored (on the DFS stack).
|
|
214
|
+
- `BLACK` = fully explored, no cycles through this node.
|
|
215
|
+
4. If a `GRAY` node is encountered during exploration, a **back edge** (cycle) is found.
|
|
216
|
+
5. Reconstruct and report the cycle path.
|
|
217
|
+
|
|
218
|
+
### Example
|
|
219
|
+
|
|
220
|
+
Given workflows: `A → B → C → A` (A references B, B references C, C references A):
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
[pi-workflows] Cycle detected: A → B → C → A. Skipping workflow "A".
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
All workflows participating in the cycle are **excluded** from the valid set.
|
|
227
|
+
|
|
228
|
+
> **Note:** The cycle detection only considers edges where the target workflow actually exists in the definitions. References to missing workflows are handled separately during [resolution](#resolution-and-loading).
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Nesting Depth
|
|
233
|
+
|
|
234
|
+
There is **no explicit depth limit** on subworkflow nesting. The practical constraint is that the reference graph must form a DAG (directed acyclic graph) — enforced by [cycle detection](#cycle-detection). As long as no cycles exist, arbitrarily deep nesting is allowed.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Loop Scope
|
|
239
|
+
|
|
240
|
+
When the agent calls `workflow_step` with action `loop`, only the **innermost scope** is restarted. The `loopPhase()` function resets the top `PathSegment`'s `phaseIndex` to `0`:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// loopPhase resets only the top of the stack
|
|
244
|
+
top.phaseIndex = 0;
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Example
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
currentPath = [{ release, 2 }, { review, 1 }]
|
|
251
|
+
parent innermost
|
|
252
|
+
|
|
253
|
+
→ loop resets { review, 0 }
|
|
254
|
+
|
|
255
|
+
currentPath = [{ release, 2 }, { review, 0 }]
|
|
256
|
+
^^^^^^^^^^^^
|
|
257
|
+
restarted to first phase
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The parent scope is unaffected. The workflow's `loopable` setting is checked on the **innermost** workflow — if `loopable: false`, the loop is rejected with an error.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Cascading Exclusion
|
|
265
|
+
|
|
266
|
+
When a workflow is excluded (due to a broken reference), other workflows that reference it may also become invalid. The loading process handles this with an iterative loop:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
let changed = true;
|
|
270
|
+
while (changed) {
|
|
271
|
+
changed = false;
|
|
272
|
+
// check each workflow's subworkflow references
|
|
273
|
+
// if any reference target is missing → exclude the referencing workflow
|
|
274
|
+
// if any exclusions occurred → set changed = true, loop again
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Example cascade
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
Workflows loaded: A, B, C, D
|
|
282
|
+
A references B
|
|
283
|
+
B references C
|
|
284
|
+
C references (non-existent) Z
|
|
285
|
+
|
|
286
|
+
Step 1: C references missing Z → exclude C
|
|
287
|
+
Step 2: B references missing C → exclude B
|
|
288
|
+
Step 3: A references missing B → exclude A
|
|
289
|
+
Step 4: No more broken references → stable
|
|
290
|
+
|
|
291
|
+
Result: Only D remains.
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Each exclusion logs a warning identifying the broken reference:
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
[pi-workflows] Workflow "C" references non-existent subworkflow "Z". Skipping.
|
|
298
|
+
[pi-workflows] Workflow "B" references non-existent subworkflow "C". Skipping.
|
|
299
|
+
[pi-workflows] Workflow "A" references non-existent subworkflow "B". Skipping.
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Shared Phase Pattern
|
|
305
|
+
|
|
306
|
+
A common convention is to organize reusable subworkflows under a `_shared/` directory:
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
workflows/
|
|
310
|
+
├── _shared/
|
|
311
|
+
│ ├── code-review/
|
|
312
|
+
│ │ ├── workflow.yaml ← show: "workflows"
|
|
313
|
+
│ │ ├── static-analysis.md
|
|
314
|
+
│ │ ├── peer-review.md
|
|
315
|
+
│ │ └── approval-gate.md
|
|
316
|
+
│ └── testing/
|
|
317
|
+
│ ├── workflow.yaml ← show: "workflows"
|
|
318
|
+
│ ├── unit-tests.md
|
|
319
|
+
│ └── integration-tests.md
|
|
320
|
+
├── release-pipeline/
|
|
321
|
+
│ ├── workflow.yaml
|
|
322
|
+
│ ├── build.md
|
|
323
|
+
│ └── deploy.md
|
|
324
|
+
└── feature-work/
|
|
325
|
+
├── workflow.yaml
|
|
326
|
+
├── design.md
|
|
327
|
+
└── implement.md
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Key points for `_shared/` workflows:
|
|
331
|
+
|
|
332
|
+
- Set `show: "workflows"` to hide them from the `/workflow` command.
|
|
333
|
+
- `commandName` and `initialMessage` can be omitted or left empty.
|
|
334
|
+
- Reference them from any other workflow: `{ subworkflow: code-review }` or `{ subworkflow: testing }`.
|
|
335
|
+
- The underscore prefix in `_shared` is a convention only — it has no special meaning to the loader. All subdirectories are scanned regardless of name.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Breadcrumb Display
|
|
340
|
+
|
|
341
|
+
When a workflow is active with nested subworkflows, the status bar and status output show a **breadcrumb trail** of the full path from root to innermost scope.
|
|
342
|
+
|
|
343
|
+
### Status bar (nested)
|
|
344
|
+
|
|
345
|
+
When `currentPath.length > 1`, the status bar shows progress at **every level** of the stack:
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
Release Pipeline > Code Review Cycle [2/3] > 🔍 Static Analysis [1/2]
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Format: `{workflowName} > {subworkflowName} [current/total] > {emoji} {phaseName} [current/total]`
|
|
352
|
+
|
|
353
|
+
- The top-level workflow name appears without progress (just the name).
|
|
354
|
+
- Every path segment (subworkflow or concrete phase) shows its own `[current/total]` progress within that scope.
|
|
355
|
+
- Subworkflow levels have no emoji (they are not `PhaseDefinition` objects).
|
|
356
|
+
- All segments are joined by `>` — no special separator.
|
|
357
|
+
- The innermost segment always has an emoji (it is the current concrete phase).
|
|
358
|
+
|
|
359
|
+
For deeply nested workflows, this extends naturally:
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
Top Workflow > Middle [1/1] > Innermost [2/2] > 🔬 Deep Phase 1 [1/2]
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Status bar (non-nested)
|
|
366
|
+
|
|
367
|
+
When `currentPath.length === 1`, the format is the same structure with a single segment:
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
Release Pipeline > 🚀 Deploy [3/4]
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Status command output
|
|
374
|
+
|
|
375
|
+
The `workflow_step` action `status` also includes a `**Path:**` line when nested:
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
**Workflow:** Release Pipeline (release)
|
|
379
|
+
**Path:** Release Pipeline > Code Review Cycle > Security Scan
|
|
380
|
+
**Phase:** 🔍 Dependency Audit [1/2] (step 5)
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Prompt injection
|
|
384
|
+
|
|
385
|
+
During context injection, the breadcrumb is embedded in the agent prompt for orientation:
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
[Workflow path: Release Pipeline > Code Review Cycle ▸ 🔍 Static Analysis]
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Full workflow.yaml Schema
|
|
394
|
+
|
|
395
|
+
For the complete `workflow.yaml` schema including all fields (role instructions, advance reminders, completion messages, etc.), see [configuration-reference.md](configuration-reference.md).
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## API Reference
|
|
400
|
+
|
|
401
|
+
### Types
|
|
402
|
+
|
|
403
|
+
| Type | Description |
|
|
404
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
405
|
+
| `SubworkflowReference` | A phase entry that delegates to another workflow. Fields: `subworkflow: true`, `workflowKey`, `resolved` |
|
|
406
|
+
| `PathSegment` | A navigation stack element. Fields: `workflowKey`, `phaseIndex` |
|
|
407
|
+
| `PhaseEntry` | Union type: `PhaseDefinition \| SubworkflowReference` |
|
|
408
|
+
| `ActiveWorkflow` | Resolved runtime state. Includes `breadcrumb` array for display |
|
|
409
|
+
|
|
410
|
+
### Type guards
|
|
411
|
+
|
|
412
|
+
| Function | Signature | Returns |
|
|
413
|
+
| ------------------- | -------------------------------- | ----------------------------------------------- |
|
|
414
|
+
| `isSubworkflowRef` | `(entry: PhaseEntry) => boolean` | `true` if entry is a `SubworkflowReference` |
|
|
415
|
+
| `isPhaseDefinition` | `(entry: PhaseEntry) => boolean` | `true` if entry is a concrete `PhaseDefinition` |
|
|
416
|
+
|
|
417
|
+
### Key functions
|
|
418
|
+
|
|
419
|
+
| Function | Module | Purpose |
|
|
420
|
+
| -------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
421
|
+
| `loadWorkflows(cwd?)` | `config/loading.ts` | Two-pass loading: directories → validate → cycle detect → resolve references |
|
|
422
|
+
| `detectCycles(definitions)` | `config/validation.ts` | DFS 3-color cycle detection; returns error messages for cycles found |
|
|
423
|
+
| `validateWorkflowDefinition(key, def)` | `config/validation.ts` | Validates a single definition; relaxed rules when `show: "workflows"` |
|
|
424
|
+
| `advancePhase(state, definitions)` | `state.ts` | Four-case stack navigation (enter/advance/done/breakout) |
|
|
425
|
+
| `loopPhase(state, definitions)` | `state.ts` | Restart innermost scope from phase 0 |
|
|
426
|
+
| `resolveActive(state, definitions)` | `state.ts` | Resolve state to `ActiveWorkflow` with breadcrumb |
|
|
427
|
+
| `phaseEntryName(entry)` | `state.ts` | Returns display name for a `PhaseEntry` — resolves subworkflow name from `resolved` or falls back to `workflowKey` |
|
|
428
|
+
| `createInitialState(key, description)` | `state.ts` | Create fresh state with single-element `currentPath` stack |
|