Static Analysis
TL;DR
Use awaitly-analyze to extract workflow structure from TypeScript, then render Mermaid/JSON/HTML artifacts for docs, review, and CI checks without running workflows.
When To Use
- You want architecture diagrams from source code, not runtime traces.
- You need deterministic workflow docs in CI.
- You want workflow diffs in PR review.
- You need rule-style diagnostics with stable slug codes.
Static Analysis Pipeline
1
Analyze Source
Parse workflow file and extract IR tree, stats, and references.
2
Render Artifact
Generate Mermaid, JSON, markdown report, or interactive HTML.
3
Apply In Workflow
Use output in docs, PRs, CI gates, and architecture reviews.
Fast start
Section titled “Fast start”# Mermaid markdown (default)npx awaitly-analyze ./src/workflows/checkout.ts
# JSONnpx awaitly-analyze ./src/workflows/checkout.ts --format=json
# Interactive HTML artifactnpx awaitly-analyze ./src/workflows/checkout.ts --html
# Workflow diffnpx awaitly-analyze --diff v1.ts v2.ts
# Strict diagnosticsnpx awaitly-analyze ./src/workflows/checkout.ts --doctorWhat the analyzer reads
Section titled “What the analyzer reads”- Step IDs from
step('id', fn, opts)as canonical names. - Workflow and step docs from
descriptionandmarkdown. - JSDoc descriptions as
jsdocDescriptionfallback. - Optional step metadata:
intent,domain,owner,tags,stateChanges,emits,calls,errorMeta.
CLI reference
Section titled “CLI reference”Core flags
Section titled “Core flags”| Flag | Default | Description |
|---|---|---|
--format=<fmt> | mermaid | mermaid, json, markdown |
--direction=<dir> | TB | TB, TD, LR, BT, RL |
--railway | off | Linear happy-path + ok/err branching |
--keys | off | Show step cache keys |
--errors / --no-errors | on | Show/hide error nodes |
--html | off | Generate interactive HTML artifact |
--html-output=<path> | auto | Write HTML to custom path |
Output control
Section titled “Output control”| Flag | Default | Description |
|---|---|---|
--types / --no-types | on | Generate .types.ts |
--test / --no-test | off | Generate test stubs |
--test-runner=<runner> | vitest | vitest, jest, mocha |
-o, --output-adjacent | off | Write output next to source |
--suffix=<value> | workflow | Adjacent output file suffix |
--no-stdout | off | Suppress stdout output |
--dsl-output=<value> | off | Write DSL output |
--write-dsl | off | Same as --dsl-output=.awaitly |
--watch | off | Re-analyze on source changes |
Interactive HTML artifact
Section titled “Interactive HTML artifact”# Writes <basename>.html next to workflow sourcenpx awaitly-analyze ./src/workflows/checkout.ts --html
# Custom pathnpx awaitly-analyze ./src/workflows/checkout.ts --html --html-output=./docs/checkout-diagram.htmlThe HTML output includes:
- Mermaid rendered client-side.
- Click-to-inspect node details.
- Built-in theme picker with persistence.
- Self-contained payload for easy sharing.
Programmatic API
Section titled “Programmatic API”import { analyze, renderStaticMermaid, renderStaticJSON, extractNodeMetadata, generateInteractiveHTML,} from 'awaitly-analyze';
const ir = analyze('./src/workflows/checkout.ts').single();
const mermaid = renderStaticMermaid(ir, { direction: 'TB', showKeys: false });const json = renderStaticJSON(ir, { pretty: true });
const metadata = extractNodeMetadata(ir);const html = generateInteractiveHTML(mermaid, metadata, { title: 'Checkout Workflow', direction: 'TB',});Fluent selection helpers
Section titled “Fluent selection helpers”| Method | Returns | Throws | Best for |
|---|---|---|---|
.single() | Single IR | If 0 or >1 workflows | Single-workflow file |
.singleOrNull() | IR or null | Never | Optional single workflow |
.all() | IR array | Never | Multi-workflow iteration |
.named(name) | Single IR | If not found | Named workflow selection |
.first() | Single IR | If empty | First workflow only |
.firstOrNull() | IR or null | Never | Safe first workflow |
Diff workflows
Section titled “Diff workflows”# Local filesnpx awaitly-analyze --diff before.ts after.ts
# HEAD vs working copynpx awaitly-analyze --diff src/workflows/checkout.ts
# Git ref vs localnpx awaitly-analyze --diff main:src/workflows/checkout.ts src/workflows/checkout.ts
# GitHub PRnpx awaitly-analyze --diff gh:#123import { analyze, diffWorkflows, renderDiffMarkdown, renderDiffJSON, renderDiffMermaid,} from 'awaitly-analyze';
const before = analyze('./v1.ts').single();const after = analyze('./v2.ts').single();
const diff = diffWorkflows(before, after, { detectRenames: true, regressionMode: false,});
const md = renderDiffMarkdown(diff, { showUnchanged: true });const json = renderDiffJSON(diff);const mermaid = renderDiffMermaid(after, diff, { showRemovedSteps: true, direction: 'TB' });Diff options
Section titled “Diff options”| Option | Default | Description |
|---|---|---|
detectRenames | true | Match same callee+position as rename |
regressionMode | false | Mark removals as regressions |
--doctor diagnostics
Section titled “--doctor diagnostics”Use strict slug-keyed diagnostics aligned with runtime and ESLint rule naming.
awaitly-analyze ./src/workflows/checkout.ts --doctorawaitly-analyze ./src/workflows/checkout.ts --doctor --format=jsonSample output:
✗ [step-require-id]:12:4 Step "<missing>" uses legacy signature without explicit ID Docs: https://jagreehal.github.io/awaitly/rules/#step-require-id Fix: Use step('id', fn, opts) instead of step(fn, opts)Analyzer output model
Section titled “Analyzer output model”interface StaticWorkflowIR { root: StaticWorkflowNode; metadata: StaticAnalysisMetadata; references: Map<string, StaticWorkflowIR>;}
interface AnalysisStats { totalSteps: number; conditionalCount: number; parallelCount: number; raceCount: number; loopCount: number; workflowRefCount: number; unknownCount: number;}Node categories:
StaticStepNodeStaticSequenceNodeStaticParallelNodeStaticRaceNodeStaticConditionalNodeStaticLoopNodeStaticWorkflowRefNodeStaticUnknownNode
Feature detection coverage
Section titled “Feature detection coverage”| Feature | Detection |
|---|---|
| Steps | step(), step.retry(), step.withTimeout() |
| Conditionals | if/else, when*, unless* |
| Loops | for, while, for-of, for-in |
| Parallel | step.all(), allAsync(), allSettledAsync() |
| Race | step.race(), anyAsync() |
| Workflow refs | Child workflow run calls |
Which Output Should You Use?
| Choice | Best for | Tradeoff |
|---|---|---|
--format=mermaid | Readable architecture diagrams in docs and PRs. | Less detail than full JSON payload. |
--format=json | CI, automation, and custom tooling. | Harder for humans to skim quickly. |
--html | Shareable deep review with click-to-inspect details. | Heavier artifact than markdown output. |
--diff | PR-level workflow change review. | Requires clean baseline/version selection. |
--doctor | Strict diagnostics and policy enforcement. | Can surface warnings that require migration work. |
Limitations
Section titled “Limitations”- Dynamic step IDs can appear as
<dynamic>. - External workflow references are only resolved when analyzable in context.