Skip to main content
This reference documents the complete workflow.yaml specification for Codemod Workflows.

Complete Example

This example demonstrates all five step types working together in a migration workflow:
workflow.yaml
version: "1"

params:
  schema:
    library:
      name: "Library"
      description: "Target library for migration"
      type: string
      default: "analytics-v2"

state:
  schema:
    - name: shards
      type: array
      items:
        type: object
        properties:
          team: { type: string }
          shardId: { type: string }

nodes:
  - id: migrate-analytics
    name: Migrate legacy analytics to analytics.track({ event, properties })
    type: automatic
    steps:
      # 1) Codemod Registry: run well-defined baseline migration first
      - name: "Run registry codemod: legacy analytics -> analytics.track"
        codemod:
          source: "@codemod/migrate-analytics-v2"
          args:
            - "legacy-analytics"
            - "analytics-v2"

      # 2) JSSG: handle project-specific patterns with programmatic logic
      - name: "Project-specific TS codemod (wrappers + key normalization)"
        js-ast-grep:
          js_file: "scripts/codemods/migrate-analytics-wrappers.ts"
          language: "typescript"
          include:
            - "src/**/*.{ts,tsx}"
          exclude:
            - "**/*.test.ts"

      # 3) AI: transform hard cases the rules miss
      - name: "AI fixups: dynamic calls + edge cases -> analytics.track object form"
        ai:
          prompt: |
            You are performing a code migration. Modify files directly.

            Goal:
            Convert any remaining legacy analytics calls into:
              analytics.track({ event: <string>, properties: <object> })

            Handle these hard cases:
            1) Dynamic event names: trackEvent(getName(), props)
              - Preserve semantics. event should become getName().
            2) Non-object props: trackEvent("X", foo)
              - Convert to properties: foo if foo is object-like, otherwise { value: foo }.

            Constraints:
            - Keep formatting roughly intact (prettier will run later).
            - If unsure about a transformation, add a TODO comment explaining why.

      # 4) YAML ast-grep: clean up simple leftovers (fast + deterministic)
      - name: "Apply cleanup rules (YAML ast-grep)"
        ast-grep:
          config_file: "rules/analytics_cleanup.yml"
          include:
            - "src/**/*.{ts,tsx,js,jsx}"
          exclude:
            - "**/*.test.*"

      # 5) Shell: format after all transformations
      - name: "Format code"
        run: npx prettier --write "src/**/*.{ts,tsx,js,jsx}"

Workflow Structure

A workflow has four top-level keys:
workflow.yaml
version: "1"
state:
  schema: []
params:
  schema: {}
templates: []
nodes: []
KeyRequiredPurpose
versionYesWorkflow schema version (currently "1")
nodesYesExecutable DAG of transformation nodes
stateNoDeclares shared-state schema for persistence
paramsNoConfigurable parameters with schema
templatesNoReusable step blocks

Nodes

Nodes are execution units in your workflow. They can be automatic or manual, depend on other nodes, and may define strategy (matrix), trigger, runtime, env, and an ordered list of steps.
version: "1"
nodes:
  - id: build
    name: Build
    type: automatic
    steps:
      - name: npm install
        run: npm ci
id
string
required
Unique identifier within the workflow.
name
string
required
Display name shown in logs and UI.
type
string
default:"automatic"
automatic or manual.
depends_on
string[]
Upstream node IDs that must complete before this node runs. See Cyclic Dependencies.
trigger
object
{ type: manual } creates an approval gate. See Manual Triggers.
strategy
object
Matrix configuration for parallel fan-out. See Matrix Strategy.
steps
array
required
Ordered list of steps to execute.
env
object
Environment variables for all steps in this node.

Steps

Steps are atomic actions inside a node. They run sequentially and each step performs one transformation or action.

JSSG Step

Executes a JavaScript/TypeScript codemod using ast-grep for pattern matching and AST manipulation.
steps:
  - name: Replace console.log with logger
    js-ast-grep:
      js_file: "scripts/codemod.ts"
      language: "tsx"
      include:
        - "**/*.ts"
        - "**/*.tsx"
      exclude:
        - "**/*.test.ts"
JSSG Step Parameters:
name
string
required
Step label.
if
string
Conditional expression to gate step execution. Supports params.x, state.x, and matrix value keys. Operators: ==, !=, >, <, &&, ||. See Variable Resolution.
js-ast-grep.js_file
string
required
Path to the JS/TS file that implements the codemod.
js-ast-grep.language
string
Target language (e.g., typescript, tsx, javascript, jsx).
js-ast-grep.include
string[]
Include glob patterns.
js-ast-grep.exclude
string[]
Exclude glob patterns.
js-ast-grep.base_path
string
Base path for resolving globs.
js-ast-grep.max_threads
number
Maximum concurrent threads.
js-ast-grep.dry_run
boolean
default:"false"
Perform a dry run without applying changes.

JSSG Documentation

Learn how to write JavaScript/TypeScript codemods.

AI Step

Calls an AI agent with a prompt for LLM-powered transformations.
steps:
  - name: Review diffs with AI
    ai:
      prompt: |
        Summarize risky changes and suggest tests.
      model: "gpt-4o"
AI Step Parameters:
name
string
required
Step label.
if
string
Conditional expression to gate step execution. See Variable Resolution.
ai.prompt
string
required
Prompt to send to the AI agent.
ai.model
string
default:"gpt-4o"
Model identifier. Overrides LLM_MODEL if set.
ai.system_prompt
string
System prompt to scope the AI agent’s behavior.
ai.max_steps
number
default:"30"
Maximum number of agent steps before stopping.
ai.llm_protocol
string
default:"openai"
LLM provider/protocol. Supported: openai, anthropic, azure_openai.
ai.endpoint
string
LLM base URL. Defaults to the provider’s standard endpoint.
ai.api_key
string
API key for LLM access.
Environment variables for AI steps
LLM_API_KEY=YOUR_KEY
LLM_PROVIDER=openai  # or anthropic, azure_openai
LLM_MODEL=gpt-4o
LLM_BASE_URL=https://api.openai.com/v1  # optional

YAML ast-grep Step

Executes ast-grep using declarative YAML rules for simple, fast pattern matching.
steps:
  - name: "Apply cleanup rules"
    ast-grep:
      config_file: "rules/config.yml"
      include:
        - "**/*.js"
      exclude:
        - "**/*.test.js"
When to use YAML ast-grep vs JSSG
  • YAML ast-grep: Simple find/replace, no conditional logic, fastest to write
  • JSSG: Conditional logic, complex AST manipulations, cross-file coordination
YAML ast-grep Step Parameters:
name
string
required
Step label.
if
string
Conditional expression to gate step execution. See Variable Resolution.
ast-grep.config_file
string
required
Path to the ast-grep configuration file (.yaml).
ast-grep.include
string[]
Include glob patterns.
ast-grep.exclude
string[]
Exclude glob patterns.
ast-grep.base_path
string
Base path for resolving globs.
ast-grep.max_threads
number
Maximum concurrent threads.

Codemod Registry Step

Runs another codemod by package name or local path.
steps:
  - name: Run registry codemod
    codemod:
      source: "@scope/package"
      args: ["--flag", "value"]
Codemod Registry Step Parameters:
name
string
required
Step label.
if
string
Conditional expression to gate step execution. See Variable Resolution.
codemod.source
string
required
Codemod source (registry package or local path). Supports version pinning: @scope/[email protected].
codemod.args
string[]
CLI arguments passed to the codemod.
codemod.env
object
Environment variables used during execution.
codemod.working_dir
string
Working directory for execution.
You can aggregate multiple codemods by adding multiple codemod steps in the same node. Steps run sequentially from top to bottom.

Shell Command Step

Runs shell commands on the host for setup, cleanup, or external tools.
steps:
  - name: Install dependencies
    run: |
      npm install
      npm run build
Shell Command Step Parameters:
name
string
required
Step label.
if
string
Conditional expression to gate step execution. See Variable Resolution.
run
string
required
Inline shell command to execute.
env
object
Step-level environment variables applied to the process environment.

Matrix Strategy

Matrix strategies fan out a node into multiple parallel tasks. Use them to shard work by team, directory, or configuration.
nodes:
  - id: matrix-codemod
    name: Matrix Codemod
    strategy:
      type: matrix
      from_state: shards
    steps:
      - name: Codemod
        run: node codemod.js --team=$team --shard=$shardId
The from_state field references an array in your workflow’s state schema. Each array item spawns a parallel task. Accessing Matrix Values: In JSSG transforms via options.matrixValues:
const transform: Transform<TSX> = async (root, options) => {
  const team = options.matrixValues?.team;
  const shardId = options.matrixValues?.shardId;
};
In shell steps as environment variables:
- name: Process shard
  run: |
    echo "Team: $team"
    echo "Shard: $shardId"
Dynamic Matrix Task Recompilation: When the array referenced by from_state changes:
  1. New tasks are created for new items
  2. Tasks are marked WontDo if their item is removed
  3. Existing tasks remain untouched if their item persists
Matrix nodes have a master task that tracks all generated tasks.

Manual Triggers

Add approval gates to pause execution until manual intervention:
nodes:
  - id: manual-approval
    name: Manual Approval
    trigger:
      type: manual
    steps:
      - name: Deploy
        run: ./deploy.sh
Resume paused tasks:
# Resume all paused tasks
npx codemod workflow resume -i <run-id> --trigger-all

# Resume a specific task
npx codemod workflow resume -i <run-id> -t <task-uuid>

Shared State

State enables workflows to persist data across runs and coordinate work:
version: "1"
state:
  schema:
    - name: shards
      type: array
      items:
        type: object
        properties:
          team: { type: string }
          shardId: { type: string }
nodes: []
You can also use object map syntax:
state:
  schema:
    shards:
      type: array
      items:
        type: object
        properties:
          team: { type: string }
          shardId: { type: string }

State Updates

SyntaxMeaningExample
KEY=VALSet state key to valuecount=10
KEY@=VALAppend value to arrayshards@={"team":"core","shardId":"1"}
Dot notationSet nested fieldsconfig.retries=5
JSON valuesUse valid JSONuser={"name":"Alice","id":123}
State updates must be valid JSON if not primitive. Updates are applied only if the task exits successfully.

Parameters

Parameters make workflows configurable and reusable:
version: "1"
params:
  schema:
    library:
      name: "Library"
      description: "Internationalization library to use"
      type: string
      default: "next-intl"
    prSize:
      name: "PR Size"
      type: string
      default: "50"
nodes: []

Accessing Parameters

In JSSG transforms via options.params:
export default function transform(root, options) {
  const library = options.params?.library || "next-intl";
}
In shell steps as PARAM_ prefixed environment variables:
- name: Run script
  run: echo "Library: $PARAM_LIBRARY"
In codemod steps forwarded with env_ prefix:
- name: Run nested codemod
  codemod:
    source: "@org/package"
  env:
    LIBRARY: ${{ params.library }}

Advanced Parameter Usage

Complete parameter patterns and examples in JSSG.

Variable Resolution

Variables can be interpolated in step configurations:
SyntaxSourceExample
${{ params.x }}Runtime parameters${{ params.branch }}
${{ env.x }}Host environment${{ env.CI }}
${{ state.x }}Shared state${{ state.counter }}
In matrix tasks, each object key becomes an environment variable (e.g., $team, $shardId). Inside steps, matrix variables are unprefixed.
Operators supported in if conditions: ==, !=, >, <, &&, ||
- name: Run only for TypeScript
  if: params.language == "typescript"
  js-ast-grep:
    js_file: "scripts/codemod.ts"

Task Statuses

StatusDescription
PendingQueued; waiting for runner
RunningCurrently executing
CompletedSucceeded; diff applied
FailedScript exited non-zero; diff discarded
AwaitingTriggerWaiting for manual approval
BlockedDependencies not finished
WontDoMatrix item removed; task skipped

Cyclic Dependencies

Workflows cannot have circular dependencies:
nodes:
  - id: a
    depends_on: [b]
    steps:
      - run: echo "A"
  - id: b
    depends_on: [a]
    steps:
      - run: echo "B"
This produces an error:
Error: Cyclic dependency detected: a → b → a

Templates

Templates define reusable step blocks (planned feature):
templates:
  - id: format
    steps:
      - name: Format
        run: npx prettier --write .

Roadmap

Container runtime support

Support for runtime: docker and other container runtimes.

Nested matrix strategies

Matrix strategies within matrix strategies for complex fan-out.

Next Steps