awaitly-analyze 0.10.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jag Reehal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,517 @@
1
+ # awaitly-analyze
2
+
3
+ Static workflow analysis for [awaitly](https://github.com/jagreehal/awaitly). Analyze workflow source code to extract structure, calculate complexity metrics, and generate visualizations.
4
+
5
+ ## CLI Usage
6
+
7
+ ```bash
8
+ # Basic usage - output to stdout
9
+ awaitly-analyze ./src/workflows/checkout.ts
10
+
11
+ # JSON format
12
+ awaitly-analyze ./src/workflows/checkout.ts --format=json
13
+
14
+ # Show step keys and change diagram direction
15
+ awaitly-analyze ./src/workflows/checkout.ts --keys --direction=LR
16
+
17
+ # Write output file adjacent to source (creates checkout.workflow.md)
18
+ awaitly-analyze ./src/workflows/checkout.ts --output-adjacent
19
+
20
+ # Custom suffix (creates checkout.diagram.md)
21
+ awaitly-analyze ./src/workflows/checkout.ts -o --suffix=diagram
22
+
23
+ # JSON format with adjacent output (creates checkout.analysis.json)
24
+ awaitly-analyze ./src/workflows/checkout.ts -o --suffix=analysis --format=json
25
+
26
+ # Write to file only, suppress stdout
27
+ awaitly-analyze ./src/workflows/checkout.ts -o --no-stdout
28
+
29
+ # Generate interactive HTML with click-to-inspect
30
+ awaitly-analyze ./src/workflows/checkout.ts --html
31
+
32
+ # Custom HTML output path
33
+ awaitly-analyze ./src/workflows/checkout.ts --html --html-output=./docs/checkout.html
34
+ ```
35
+
36
+ ### CLI Options
37
+
38
+ | Option | Default | Description |
39
+ |--------|---------|-------------|
40
+ | `--format=<format>` | `mermaid` | Output format: `mermaid` or `json` |
41
+ | `--html` | - | Generate interactive HTML file (Mermaid CDN + click-to-inspect) |
42
+ | `--html-output=<path>` | `<basename>.html` | Output path for the HTML file |
43
+ | `--keys` | - | Show step cache keys in diagram |
44
+ | `--direction=<dir>` | `TB` | Diagram direction: `TB`, `LR`, `BT`, `RL` |
45
+ | `--output-adjacent`, `-o` | - | Write output file next to source file |
46
+ | `--suffix=<value>` | `workflow` | Configurable suffix for output file |
47
+ | `--no-stdout` | - | Suppress stdout when writing to file (requires `-o`) |
48
+ | `--dsl-output=<value>` | `off` | Write DSL: `off`, `.awaitly`, or custom path (for visualization) |
49
+ | `--write-dsl` | - | Shorthand for `--dsl-output=.awaitly` |
50
+ | `--help`, `-h` | - | Show help message |
51
+
52
+ ### Output File Naming
53
+
54
+ When using `--output-adjacent`:
55
+ - Mermaid format: `{basename}.{suffix}.md`
56
+ - JSON format: `{basename}.{suffix}.json`
57
+
58
+ Example: `checkout.ts` with `--suffix=workflow` produces `checkout.workflow.md`
59
+
60
+ ## Features
61
+
62
+ - **Static Analysis** - Extract workflow structure from TypeScript source without execution
63
+ - **Path Generation** - Enumerate all possible execution paths through a workflow
64
+ - **Complexity Metrics** - Calculate cyclomatic complexity, cognitive complexity, and more
65
+ - **Mermaid Diagrams** - Generate flowchart visualizations
66
+ - **Test Matrix** - Generate test coverage matrices for workflow paths
67
+ - **Cross-Workflow Composition** - Analyze dependencies between workflows
68
+
69
+ ## Installation
70
+
71
+ ```bash
72
+ npm install awaitly-analyze ts-morph
73
+ # or
74
+ pnpm add awaitly-analyze ts-morph
75
+ ```
76
+
77
+ `ts-morph` is a required peer dependency.
78
+
79
+ ## Quick Start
80
+
81
+ ```typescript
82
+ import {
83
+ analyze,
84
+ generatePaths,
85
+ calculateComplexity,
86
+ renderStaticMermaid,
87
+ } from 'awaitly-analyze';
88
+
89
+ // Analyze a workflow file
90
+ const ir = analyze('./src/workflows/checkout.ts').single();
91
+
92
+ // Generate all possible paths
93
+ const paths = generatePaths(ir);
94
+ console.log(`Found ${paths.length} unique execution paths`);
95
+
96
+ // Calculate complexity metrics
97
+ const metrics = calculateComplexity(ir);
98
+ console.log(`Cyclomatic complexity: ${metrics.cyclomaticComplexity}`);
99
+
100
+ // Generate Mermaid diagram
101
+ const mermaid = renderStaticMermaid(ir);
102
+ console.log(mermaid);
103
+ ```
104
+
105
+ ## API Reference
106
+
107
+ ### Analyzing Workflows
108
+
109
+ #### `analyze(filePath, options?)`
110
+
111
+ Returns a fluent interface for analyzing workflows in a file.
112
+
113
+ ```typescript
114
+ import { analyze } from 'awaitly-analyze';
115
+
116
+ // Single workflow file - get the workflow directly
117
+ const ir = analyze('./checkout.ts').single();
118
+
119
+ // Multiple workflows - get all as array
120
+ const workflows = analyze('./workflows.ts').all();
121
+
122
+ // Get specific workflow by name
123
+ const checkout = analyze('./workflows.ts').named('checkoutWorkflow');
124
+
125
+ // Safe access (returns null instead of throwing)
126
+ const ir = analyze('./checkout.ts').singleOrNull();
127
+ const first = analyze('./workflows.ts').firstOrNull();
128
+ ```
129
+
130
+ #### `analyze.source(code, options?)`
131
+
132
+ Analyze workflow source code from a string.
133
+
134
+ ```typescript
135
+ const ir = analyze.source(`
136
+ const workflow = createWorkflow({
137
+ fetchUser: async (id: string) => ({ id, name: 'Alice' }),
138
+ });
139
+
140
+ async function run(id: string) {
141
+ return await workflow(async (step, deps) => {
142
+ const user = await step('fetchUser', () => deps.fetchUser(id), { key: 'user' });
143
+ return user;
144
+ });
145
+ }
146
+ `).single();
147
+ ```
148
+
149
+ ### Path Generation
150
+
151
+ #### `generatePaths(ir, options?)`
152
+
153
+ Generate all unique execution paths through a workflow.
154
+
155
+ ```typescript
156
+ import { generatePaths, calculatePathStatistics } from 'awaitly-analyze';
157
+
158
+ const paths = generatePaths(ir, { maxPaths: 100 });
159
+
160
+ // Get statistics
161
+ const stats = calculatePathStatistics(paths);
162
+ console.log(`Total paths: ${stats.totalPaths}`);
163
+ console.log(`Shortest path: ${stats.shortestPathLength} steps`);
164
+ console.log(`Longest path: ${stats.longestPathLength} steps`);
165
+ ```
166
+
167
+ #### `generatePathsWithMetadata(ir, options?)`
168
+
169
+ Generate paths with additional metadata about limit hits.
170
+
171
+ ```typescript
172
+ import { generatePathsWithMetadata } from 'awaitly-analyze';
173
+
174
+ const { paths, limitHit } = generatePathsWithMetadata(ir, { maxPaths: 50 });
175
+
176
+ if (limitHit) {
177
+ console.log('Warning: Path limit reached, not all paths generated');
178
+ }
179
+ ```
180
+
181
+ ### Complexity Metrics
182
+
183
+ #### `calculateComplexity(ir)`
184
+
185
+ Calculate complexity metrics for a workflow.
186
+
187
+ ```typescript
188
+ import { calculateComplexity } from 'awaitly-analyze';
189
+
190
+ const metrics = calculateComplexity(ir);
191
+
192
+ console.log(`Cyclomatic complexity: ${metrics.cyclomaticComplexity}`);
193
+ console.log(`Cognitive complexity: ${metrics.cognitiveComplexity}`);
194
+ console.log(`Max nesting depth: ${metrics.maxDepth}`);
195
+ console.log(`Max parallel breadth: ${metrics.maxParallelBreadth}`);
196
+ console.log(`Decision points: ${metrics.decisionPoints}`);
197
+ console.log(`Path count: ${metrics.pathCount}`);
198
+ ```
199
+
200
+ #### `assessComplexity(metrics, thresholds?)`
201
+
202
+ Get a complexity assessment with warnings.
203
+
204
+ ```typescript
205
+ import {
206
+ analyze,
207
+ calculateComplexity,
208
+ assessComplexity,
209
+ formatComplexitySummary
210
+ } from 'awaitly-analyze';
211
+
212
+ const ir = analyze('./checkout.ts').single();
213
+ const metrics = calculateComplexity(ir);
214
+ const assessment = assessComplexity(metrics);
215
+ console.log(formatComplexitySummary(metrics, assessment));
216
+ ```
217
+
218
+ ### Mermaid Diagrams
219
+
220
+ #### `renderStaticMermaid(ir, options?)`
221
+
222
+ Generate a Mermaid flowchart diagram.
223
+
224
+ ```typescript
225
+ import { renderStaticMermaid } from 'awaitly-analyze';
226
+
227
+ const mermaid = renderStaticMermaid(ir, {
228
+ direction: 'TB', // 'TB' | 'LR' | 'BT' | 'RL'
229
+ includeKeys: true,
230
+ includeDescriptions: true,
231
+ });
232
+
233
+ // Use in markdown:
234
+ // ```mermaid
235
+ // ${mermaid}
236
+ // ```
237
+ ```
238
+
239
+ #### `renderPathsMermaid(ir, paths, options?)`
240
+
241
+ Generate a diagram highlighting specific paths.
242
+
243
+ ```typescript
244
+ import { renderPathsMermaid, generatePaths } from 'awaitly-analyze';
245
+
246
+ const paths = generatePaths(ir);
247
+ const diagram = renderPathsMermaid(ir, paths.slice(0, 3));
248
+ ```
249
+
250
+ ### Test Matrix
251
+
252
+ #### `generateTestMatrix(paths)`
253
+
254
+ Generate a test coverage matrix from paths.
255
+
256
+ ```typescript
257
+ import { generateTestMatrix, formatTestMatrixMarkdown } from 'awaitly-analyze';
258
+
259
+ const paths = generatePaths(ir);
260
+ const matrix = generateTestMatrix(paths);
261
+
262
+ // Format as markdown table
263
+ console.log(formatTestMatrixMarkdown(matrix));
264
+
265
+ // Or as code
266
+ import { formatTestMatrixAsCode } from 'awaitly-analyze';
267
+ console.log(formatTestMatrixAsCode(matrix));
268
+ ```
269
+
270
+ ### Cross-Workflow Composition
271
+
272
+ #### `analyzeWorkflowGraph(files, options?)`
273
+
274
+ Analyze dependencies between workflows across multiple files.
275
+
276
+ ```typescript
277
+ import {
278
+ analyzeWorkflowGraph,
279
+ getTopologicalOrder,
280
+ renderGraphMermaid,
281
+ } from 'awaitly-analyze';
282
+
283
+ const graph = analyzeWorkflowGraph([
284
+ './src/workflows/checkout.ts',
285
+ './src/workflows/payment.ts',
286
+ './src/workflows/shipping.ts',
287
+ ]);
288
+
289
+ // Get execution order (workflows with no dependencies first)
290
+ const order = getTopologicalOrder(graph);
291
+ console.log('Execution order:', order.map(n => n.name).join(' -> '));
292
+
293
+ // Visualize the dependency graph
294
+ const diagram = renderGraphMermaid(graph);
295
+ ```
296
+
297
+ ### Interactive HTML
298
+
299
+ Generate a self-contained HTML file with an interactive Mermaid diagram and a click-to-inspect panel. The output includes 6 color themes, system preference auto-detection, and localStorage persistence.
300
+
301
+ #### `extractNodeMetadata(ir)`
302
+
303
+ Walk the IR tree and produce a `WorkflowMetadata` object with per-node details (step IDs, callees, retry/timeout config, types, source locations) keyed by Mermaid node ID.
304
+
305
+ ```typescript
306
+ import { analyze, extractNodeMetadata } from 'awaitly-analyze';
307
+ import type { WorkflowMetadata } from 'awaitly-analyze';
308
+
309
+ const ir = analyze('./checkout.ts').single();
310
+ const metadata: WorkflowMetadata = extractNodeMetadata(ir);
311
+ ```
312
+
313
+ #### `generateInteractiveHTML(mermaidText, metadata, options?)`
314
+
315
+ Combine Mermaid text from `renderStaticMermaid()` with metadata from `extractNodeMetadata()` into a complete HTML string.
316
+
317
+ ```typescript
318
+ import {
319
+ analyze,
320
+ renderStaticMermaid,
321
+ extractNodeMetadata,
322
+ generateInteractiveHTML,
323
+ } from 'awaitly-analyze';
324
+ import type { InteractiveHTMLOptions } from 'awaitly-analyze';
325
+
326
+ const ir = analyze('./checkout.ts').single();
327
+ const mermaid = renderStaticMermaid(ir);
328
+ const metadata = extractNodeMetadata(ir);
329
+ const html = generateInteractiveHTML(mermaid, metadata, {
330
+ theme: 'midnight', // 'midnight' | 'ocean' | 'ember' | 'forest' | 'daylight' | 'paper'
331
+ title: 'Checkout Workflow', // defaults to workflow name
332
+ direction: 'TB', // diagram direction
333
+ });
334
+
335
+ import { writeFileSync } from 'node:fs';
336
+ writeFileSync('checkout.html', html);
337
+ ```
338
+
339
+ **`InteractiveHTMLOptions`:**
340
+
341
+ | Option | Type | Default | Description |
342
+ |--------|------|---------|-------------|
343
+ | `title` | `string` | workflow name | Page title |
344
+ | `theme` | `string` | auto-detect | Initial color theme |
345
+ | `mermaidCdnUrl` | `string` | latest v11 | Mermaid CDN URL |
346
+ | `direction` | `"TB" \| "LR" \| "BT" \| "RL"` | `"TB"` | Diagram direction |
347
+
348
+ ### Workflow Diagram DSL
349
+
350
+ #### `renderWorkflowDSL(ir)`
351
+
352
+ Produce a state-machine-like DSL for xstate-style visualization (states and transitions with event labels). Types are defined in `awaitly/workflow`; the analyzer emits DSL that conforms to them.
353
+
354
+ ```typescript
355
+ import { analyze, renderWorkflowDSL, writeDSLToAwaitlyDir } from 'awaitly-analyze';
356
+ import type { WorkflowDiagramDSL } from 'awaitly/workflow';
357
+
358
+ const ir = analyze('./checkout.ts').single();
359
+ const dsl: WorkflowDiagramDSL = renderWorkflowDSL(ir);
360
+
361
+ // Optional: write to .awaitly/dsl/ (default) or a custom folder
362
+ await writeDSLToAwaitlyDir(dsl, { rootDir: process.cwd() });
363
+ // Custom folder: await writeDSLToAwaitlyDir(dsl, { rootDir: process.cwd(), outputDir: 'dist/dsl' });
364
+ ```
365
+
366
+ **Snapshot alignment:** When a workflow is running, `WorkflowSnapshot.execution.currentStepId` holds the step key. DSL step state ids use the same step key so a visualizer can highlight the current node. See `awaitly/workflow` diagram-dsl types for details.
367
+
368
+ ### JSON Output
369
+
370
+ #### `renderStaticJSON(ir, options?)`
371
+
372
+ Serialize workflow IR to JSON.
373
+
374
+ ```typescript
375
+ import { renderStaticJSON } from 'awaitly-analyze';
376
+
377
+ const json = renderStaticJSON(ir, { pretty: true });
378
+ fs.writeFileSync('workflow.json', json);
379
+ ```
380
+
381
+ ## Detected Patterns
382
+
383
+ The analyzer detects the following awaitly patterns:
384
+
385
+ - `createWorkflow()` - Standard workflow creation
386
+ - `run()` - Inline workflow execution
387
+ - `createSagaWorkflow()` - Saga workflow creation
388
+ - `runSaga()` - Inline saga execution
389
+
390
+ Within workflows, it detects:
391
+
392
+ - `step('id', fn, opts?)` - Single steps (string ID required) with retry/timeout options
393
+ - `step.parallel()` / `allAsync()` / `allSettledAsync()` - Parallel execution
394
+ - `step.race()` / `anyAsync()` - Race execution
395
+ - `step.sleep(id, duration, opts?)` - Sleep steps (ID required as first argument)
396
+ - `step.retry(id, operation, opts)` - Retry wrappers (ID required as first argument)
397
+ - `step.withTimeout(id, operation, opts)` - Timeout wrappers (ID required as first argument)
398
+ - `step.try(id, operation, opts)` - Try/catch steps (ID required as first argument)
399
+ - `step.fromResult(id, operation, opts)` - FromResult steps (ID required as first argument)
400
+ - `step.getWritable()` / `step.getReadable()` / `step.streamForEach()` - Streaming
401
+ - `when()` / `unless()` / `whenOr()` / `unlessOr()` - Conditional helpers
402
+ - `if/else`, `switch` - Control flow
403
+ - `for`, `while`, `forEach`, `map` - Loops
404
+ - Saga steps with compensation (`saga.step()`, `saga.tryStep()`)
405
+
406
+ ## Import Styles
407
+
408
+ The analyzer supports various import styles:
409
+
410
+ ```typescript
411
+ // Named imports
412
+ import { createWorkflow, run } from 'awaitly';
413
+
414
+ // Aliased imports
415
+ import { createWorkflow as cw } from 'awaitly';
416
+
417
+ // Namespace imports
418
+ import * as Awaitly from 'awaitly';
419
+ Awaitly.createWorkflow({...});
420
+
421
+ // Default imports
422
+ import Awaitly from 'awaitly';
423
+ Awaitly.run(async (step) => {...});
424
+ ```
425
+
426
+ ## Types
427
+
428
+ Key types exported from the package:
429
+
430
+ ```typescript
431
+ import type {
432
+ // IR nodes
433
+ StaticWorkflowIR,
434
+ StaticFlowNode,
435
+ StaticStepNode,
436
+ StaticParallelNode,
437
+ StaticConditionalNode,
438
+
439
+ // Paths
440
+ WorkflowPath,
441
+ PathStatistics,
442
+
443
+ // Complexity
444
+ ComplexityMetrics,
445
+ ComplexityAssessment,
446
+
447
+ // Test matrix
448
+ TestMatrix,
449
+ TestPath,
450
+
451
+ // Graph
452
+ WorkflowGraph,
453
+ WorkflowGraphNode,
454
+
455
+ // Interactive HTML
456
+ NodeMetadata,
457
+ WorkflowMetadata,
458
+ InteractiveHTMLOptions,
459
+ } from 'awaitly-analyze';
460
+ ```
461
+
462
+ ### API types reference
463
+
464
+ Main static-analysis node types and when fields are populated:
465
+
466
+ - **StaticWorkflowNode** (root): `workflowName`, `source`, `dependencies`, `children`, `description`, `markdown`, `jsdocDescription?`, `errorTypes`.
467
+ `description` and `markdown` are set only for `createWorkflow` / `createSagaWorkflow` (from options or deps). They are undefined for `run()` / `runSaga()` (no options object). `jsdocDescription` is extracted from JSDoc above the workflow variable when present.
468
+
469
+ - **StaticStepNode**: `stepId`, `callee`, `name`, `key`, `description`, `markdown`, `jsdocDescription?`, `retry`, `timeout`.
470
+ `stepId` is the required first argument for all step types: `step('id', fn, opts)`, `step.sleep('id', duration, opts?)`, `step.retry('id', operation, opts)`, `step.withTimeout('id', operation, opts)`, `step.try('id', operation, opts)`, `step.fromResult('id', operation, opts)`. Legacy `step(fn, opts)` is still parsed but yields `stepId: "<missing>"` and a warning. `description` and `markdown` come from step options; `jsdocDescription` is extracted from JSDoc above the step statement when present.
471
+
472
+ - **StaticSagaStepNode**: `callee`, `name`, `description`, `markdown`, `jsdocDescription?`, `hasCompensation`, `compensationCallee`, `isTryStep`.
473
+ `description` and `markdown` come from saga step options (e.g. `saga.step(fn, { description, markdown })`). `jsdocDescription` is extracted from JSDoc above the saga step statement when present.
474
+
475
+ - **DependencyInfo**: `name`, `typeSignature?`, `errorTypes`.
476
+ `typeSignature` is the TypeScript type of the dependency (e.g. the function type), when the type checker is available; it may be undefined. `errorTypes` is not yet inferred from types and is typically empty.
477
+
478
+ ### JSDoc
479
+
480
+ The analyzer extracts JSDoc comments from workflow declarations (`createWorkflow` / `createSagaWorkflow` variable statements) and from step call sites (the statement containing `await step(...)`, `step.sleep(...)`, `saga.step(...)`, etc.). Extracted text is exposed as **`jsdocDescription`** on the root (`StaticWorkflowNode`) and on step nodes (`StaticStepNode`, `StaticSagaStepNode`). Only the main description (text before the first `@tag`) is extracted; `@param` / `@returns` are not parsed into separate fields. Option-based `description` and `markdown` remain the canonical documentation fields and take precedence for display; JSDoc is additive so consumers can use `description ?? jsdocDescription` for fallback.
481
+
482
+ ### JSON output shape
483
+
484
+ The output of `renderStaticJSON(ir)` has this structure. Agents and doc generators can use it to parse or validate the JSON.
485
+
486
+ - **Top level**: `{ root, metadata?, references? }`
487
+ - `root`: StaticWorkflowNode (see below)
488
+ - `metadata`: `{ analyzedAt, filePath, tsVersion?, warnings?, stats? }`
489
+ - `references`: object mapping workflow name to `{ root, metadata? }` (when inlined)
490
+
491
+ - **StaticWorkflowNode** (`root`): `type: "workflow"`, `id`, `workflowName`, `source?`, `dependencies[]`, `errorTypes[]`, `children[]`, `description?`, `markdown?`, `jsdocDescription?`, `name?`, `key?`, `location?`
492
+
493
+ - **DependencyInfo** (each entry in `dependencies`): `name`, `typeSignature?`, `errorTypes[]`
494
+
495
+ - **Flow nodes** (`children` and nested): discriminated by `type`:
496
+ - `"step"`: `id`, `stepId`, `name?`, `key?`, `callee?`, `description?`, `markdown?`, `jsdocDescription?`, `retry?`, `timeout?`, `location?`
497
+ - `"saga-step"`: `id`, `name?`, `callee?`, `description?`, `markdown?`, `jsdocDescription?`, `hasCompensation`, `compensationCallee?`, `isTryStep?`, `location?`
498
+ - `"sequence"`: `id`, `children[]`
499
+ - `"parallel"`: `id`, `children[]`, `mode` ("all" | "allSettled"), `callee?`
500
+ - `"race"`: `id`, `children[]`, `callee?`
501
+ - `"conditional"`: `id`, `condition`, `consequent[]`, `alternate?`, `helper?`, `defaultValue?`
502
+ - `"switch"`: `id`, `expression`, `cases[]` (each: `value?`, `isDefault`, `body[]`)
503
+ - `"loop"`: `id`, `loopType`, `body[]`, `iterSource?`, `boundKnown`, `boundCount?`
504
+ - `"stream"`: `id`, `streamType`, `namespace?`, `options?`, `callee?`
505
+ - `"workflow-ref"`: `id`, `workflowName`, `resolved`, `resolvedPath?`, `inlinedIR?`
506
+ - `"unknown"`: `id`, `reason`, `sourceCode?`
507
+
508
+ A JSON Schema for this structure is available at `schema/static-workflow-ir.schema.json` in this package.
509
+
510
+ ## Requirements
511
+
512
+ - Node.js >= 22
513
+ - TypeScript project with `ts-morph` >= 27.0.2
514
+
515
+ ## License
516
+
517
+ MIT