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
Analyze Source
Parse workflow file and extract IR tree, stats, and references.
Render Artifact
Generate Mermaid, JSON, markdown report, or interactive HTML.
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.