Skip to content

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.

Terminal window
# Mermaid markdown (default)
npx awaitly-analyze ./src/workflows/checkout.ts
# JSON
npx awaitly-analyze ./src/workflows/checkout.ts --format=json
# Interactive HTML artifact
npx awaitly-analyze ./src/workflows/checkout.ts --html
# Workflow diff
npx awaitly-analyze --diff v1.ts v2.ts
# Strict diagnostics
npx awaitly-analyze ./src/workflows/checkout.ts --doctor
  • Step IDs from step('id', fn, opts) as canonical names.
  • Workflow and step docs from description and markdown.
  • JSDoc descriptions as jsdocDescription fallback.
  • Optional step metadata: intent, domain, owner, tags, stateChanges, emits, calls, errorMeta.
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
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
Terminal window
# Writes <basename>.html next to workflow source
npx awaitly-analyze ./src/workflows/checkout.ts --html
# Custom path
npx awaitly-analyze ./src/workflows/checkout.ts --html --html-output=./docs/checkout-diagram.html

The HTML output includes:

  • Mermaid rendered client-side.
  • Click-to-inspect node details.
  • Built-in theme picker with persistence.
  • Self-contained payload for easy sharing.
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',
});
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
Terminal window
# Local files
npx awaitly-analyze --diff before.ts after.ts
# HEAD vs working copy
npx awaitly-analyze --diff src/workflows/checkout.ts
# Git ref vs local
npx awaitly-analyze --diff main:src/workflows/checkout.ts src/workflows/checkout.ts
# GitHub PR
npx awaitly-analyze --diff gh:#123
import {
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' });
Option Default Description
detectRenames true Match same callee+position as rename
regressionMode false Mark removals as regressions

Use strict slug-keyed diagnostics aligned with runtime and ESLint rule naming.

Terminal window
awaitly-analyze ./src/workflows/checkout.ts --doctor
awaitly-analyze ./src/workflows/checkout.ts --doctor --format=json

Sample 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)
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:

  • StaticStepNode
  • StaticSequenceNode
  • StaticParallelNode
  • StaticRaceNode
  • StaticConditionalNode
  • StaticLoopNode
  • StaticWorkflowRefNode
  • StaticUnknownNode
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?

ChoiceBest forTradeoff
--format=mermaidReadable architecture diagrams in docs and PRs.Less detail than full JSON payload.
--format=jsonCI, automation, and custom tooling.Harder for humans to skim quickly.
--htmlShareable deep review with click-to-inspect details.Heavier artifact than markdown output.
--diffPR-level workflow change review.Requires clean baseline/version selection.
--doctorStrict diagnostics and policy enforcement.Can surface warnings that require migration work.
  • Dynamic step IDs can appear as <dynamic>.
  • External workflow references are only resolved when analyzable in context.