Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Definitively is an FSM-based workflow runner for CLI commands, LLM sessions, and git steps. You describe workflows as YAML state machines; definitively executes them with OTP :gen_statem, classifying each step into typed outcomes rather than raw exit codes.

When to use definitively

Use definitively when you want:

  • Repeatable automation — lint → test → fix loops, release pipelines, agent-driven quality gates
  • Explicit control flow — states, transitions, and failure paths visible in YAML
  • Mixed executors — shell commands and LLM agents in one program
  • Workspace-local config — programs, prompts, and visualizations live under .definitively/ in your repo

Mental model

workspace root
  └── .definitively/
        programs/*.yml   ← workflow definitions
        prompts/*.md     ← LLM prompt files
        visualizations/  ← graph output from `visualize`

definitively run program.yml
  → load & validate program
  → start FSM at initial state
  → execute active state's node (CLI or LLM)
  → classify outcome (success, failure, partial, …)
  → transition via state's `on:` map
  → repeat until a final state

What this book covers

PartTopics
IInstall, first run, core concepts
IIWorkspace layout and scaffolding
IIIYAML program authoring
IVReal-world patterns
VCLI reference
VIAll install channels
AppendicesCheat sheets, contributing, releases

Try it: Continue to Quick start to install and run your first program in five minutes.

Quick start

Get definitively installed, scaffold a workspace, and run the example program.

Install

The fastest path is the curl installer (Linux x86_64 or macOS arm64):

curl -fsSL https://raw.githubusercontent.com/Industrial/definitively/main/install.sh | bash

Pin a release:

curl -fsSL https://raw.githubusercontent.com/Industrial/definitively/main/install.sh | bash -s -- --version definitively-v0.2.0

Other channels (Hex, Nix, Homebrew) are in Install channels.

Scaffold a workspace

From your project root:

cd /path/to/your/repo
definitively init

This copies templates into .definitively/ (skips existing files). Use --force to overwrite.

Run the example program

Programs must live under .definitively/:

definitively run "$PWD/.definitively/programs/example.yml"

On success you see workflow finished and exit code 0.

Visualize the workflow

Default mode writes DOT and PNG under .definitively/visualizations/:

definitively visualize "$PWD/.definitively/programs/example.yml"

Requires Graphviz dot for PNG; DOT is always written.

Next steps

Core concepts

Finite state machine

Every program is a finite state machine (FSM):

  • One initial state where execution begins
  • Active states run a node (CLI command or LLM session)
  • Passive states wait for an external event (e.g. approval label)
  • Final states end the run (success or failure)

Transitions are declared on each state under on: — mapping an outcome label to the next state name.

Nodes

A node is a reusable unit of work referenced by active states:

KindRuns
cliA shell command (argv list)
gitStructured git operations (status, commit, push, …)
ghGitHub CLI operations (pr_create, run_watch, …)
llmAn LLM agent via configurable command + prompt file

Nodes define outcome rules that classify raw results (exit code, timeout, JSON, signals) into labels like success, failure, or partial.

Outcomes

Definitively does not treat exit code 0 as success blindly. Each node declares how to interpret results:

outcome:
  success:
    - exit_code: 0
  failure:
    - exit_code: {neq: 0}

The engine picks the first matching label, then looks up that label in the current state’s on: map to decide the next state.

Workspace

The workspace root is the directory containing .definitively/. Definitively infers it from the program path (parent of .definitively/). Override with DEFINITIVELY_WORKSPACE.

Runs

A run is one execution of a program from initial state through transitions to a final state. The CLI command definitively run starts a run synchronously and exits when the workflow finishes or gets stuck.

Try it: Open .definitively/programs/example.yml after definitively init and trace each state against States and transitions.

The .definitively/ layout

The workspace root is the directory containing .definitively/, not the folder itself.

<workspace-root>/
  .definitively/
    env.example          # environment variable hints
    programs/
      *.yml              # workflow program definitions
    prompts/
      *.md               # LLM prompt files (referenced by program YAML)
    visualizations/
      .gitkeep           # default output for `definitively visualize`
      *.dot / *.png      # generated graphs (typically gitignored)

Rules

  1. Program paths must be under .definitively/Definitively.Workspace resolves layout from the program file location.
  2. Prompt paths in LLM nodes are relative to the workspace root (e.g. .definitively/prompts/fix-lint.md).
  3. Visualizations default to .definitively/visualizations/<program-basename>.{dot,png}.

Templates vs live workspace

definitively init copies from packaged templates in the installed escript (priv/templates/definitively/). Your live .definitively/ is what runs execute against.

Try it: Run definitively init in a scratch repo and compare the tree to the template paths in the definitively repository.

Environment variables

VariableDefaultPurpose
DEFINITIVELY_WORKSPACEParent of .definitively/ from program pathWorkspace root for runs, init, and visualize
DEFINITIVELY_LOG_LEVELINFOLogging verbosity: TRACE, DEBUG, INFO, WARNING, ERROR
DEFINITIVELY_LLM_COMMANDStub in devOverride default LLM runner (space-separated argv prefix)
DEFINITIVELY_FROM_SOURCEunsetWhen 1, devenv builds escript from definitively/ on shell enter

Copy hints from .definitively/env.example after definitively init.

Logging

Set log level before running workflows:

export DEFINITIVELY_LOG_LEVEL=DEBUG
definitively run "$PWD/.definitively/programs/example.yml"

Trace-level logs show validation, node execution, and transition details.

Scaffolding with init

definitively init              # copy templates; skip existing files
definitively init --force      # overwrite existing template files

What gets copied

PathPurpose
programs/example.ymlMinimal passive → active → final workflow
prompts/example.mdSample LLM prompt
env.exampleEnvironment variable hints
visualizations/.gitkeepEnsures visualize output directory exists

Override workspace root

When your shell cwd is not the repo root:

export DEFINITIVELY_WORKSPACE=/path/to/repo
definitively init

Try it: Run init, then definitively run and definitively visualize on example.yml as in Quick start.

Program structure

Every program YAML has three top-level keys:

program:
  id: my_workflow
  version: 1
  initial: idle

states:
  # state_name: { type, node?, on: { label: next_state } }

nodes:
  # node_id: { kind, command?, outcome: { ... } }

program block

FieldRequiredDescription
idyesStable identifier (atom in Elixir)
versionyesInteger schema version
initialyesName of the starting state

states block

Map of state names to definitions. See States and transitions.

nodes block

Map of node IDs to definitions. Active states reference nodes by ID. See CLI nodes and LLM nodes.

Minimal example

program:
  id: example
  version: 1
  initial: idle
states:
  idle:
    type: passive
    on:
      start: run
  run:
    type: active
    node: echo
    on:
      success: done
  done:
    type: final
nodes:
  echo:
    kind: cli
    command: ["sh", "-c", "exit 0"]
    outcome:
      success:
        - exit_code: 0
      failure:
        - exit_code: {neq: 0}

Try it: Validate structure by running definitively run — load errors print before execution starts.

States and transitions

State types

TypeBehavior
passiveWaits for an external transition label (e.g. approval)
activeRuns the referenced node when entered
finalTerminal state; ends the run

Active states

Must declare node: <node_id> matching a key in nodes:.

lint:
  type: active
  node: moon_lint
  on:
    success: doctor
    failure: fix_lint
    partial: fix_lint

Transitions (on:)

Each key is an outcome label from the node’s outcome rules. Each value is the next state name.

Common labels:

  • success — primary happy path
  • failure — hard failure
  • partial — incomplete but recoverable (often routes to a fix state)
  • retry — explicit retry loop (classified as failure internally, but used as transition key)

Initial and final states

  • program.initial must name an existing state.
  • At least one type: final state is required.
  • At least one final state must be reachable from initial (validated at load time).

Passive states

Used for approval gates or manual triggers. The engine transitions when a label is supplied (e.g. auto-approve in tests).

Try it: Draw your state graph with definitively visualize program.yml.

CLI nodes

nodes:
  moon_lint:
    kind: cli
    command: ["moon", "run", "definitively:lint"]
    timeout_ms: 300000
    outcome:
      success:
        - exit_code: 0
      failure:
        - exit_code: {neq: 0}
      partial:
        - exit_code: 0

Fields

FieldRequiredDescription
kindyesMust be cli
commandyesargv list (no shell unless you invoke sh -c)
timeout_msnoKill subprocess after this many milliseconds
cwdnoWorking directory (default: workspace root)
outcomeyesOutcome rules — see Outcome rules

Tips

  • Prefer argv lists over shell strings for clarity and safety.
  • Set timeout_ms on long-running commands (tests, builds).
  • Map both failure and partial when you want fix loops on non-zero exits.

Try it: Add a CLI node that runs git status and routes on exit code.

Git nodes

Git nodes run structured git operations without shell wrappers. Declare an action and optional options; definitively builds the git argv, runs the command, and parses output into signals and data.

Example

nodes:
  repo_status:
    kind: git
    action: status
    outcome:
      success:
        - signal: clean
      partial:
        - signal: dirty

  ship_commit:
    kind: git
    action: commit
    options:
      message: "chore: ship"
      add: all
    outcome:
      success:
        - exit_code: 0
      failure:
        - exit_code: {neq: 0}

Fields

FieldRequiredDescription
kindyesMust be git
actionyesGit operation (see table below)
optionsnoAction-specific parameters
cwdnoWorking directory (default: workspace root)
timeout_msnoSubprocess timeout
outcomeyesOutcome rules — see Outcome rules

Actions

ActionPurposeOptionsSignals / data
statusWorking tree snapshotclean, dirty, ahead, behind
diffShow changesstaged, stathas_changes
addStage filesall: true or paths: [...]exit code
commitCreate commitmessage, add, amend, allow_emptyexit code
pushPush refsremote, branch, tags, set_upstreamexit code
tagCreate tagname, message, annotate, pushexit code

The commit action accepts add: all or add: [paths] to stage before committing in one node.

Prerequisites

  • git on PATH
  • For commit/tag: configure user.name and user.email in the repo

Node catalog

Copy-paste fragments from .definitively/nodes/git.yml (installed via definitively init).

Try it: Add a status node to a program and route partial (dirty) to a commit node.

GitHub (gh) nodes

GitHub CLI nodes automate pull requests and Actions workflows. Like git nodes, they use structured action and options instead of raw shell commands.

Example

nodes:
  open_pr:
    kind: gh
    action: pr_create
    options:
      title: "Automated PR"
      body: "Opened by definitively"
    outcome:
      success:
        - exit_code: 0
      failure:
        - exit_code: {neq: 0}

  watch_ci:
    kind: gh
    action: run_watch
    options:
      workflow: definitively-ci.yml
    timeout_ms: 900000
    outcome:
      success:
        - exit_code: 0
      failure:
        - exit_code: {neq: 0}

Fields

FieldRequiredDescription
kindyesMust be gh
actionyesGitHub CLI operation (see table below)
optionsnoAction-specific parameters
timeout_msnoDefault 900000 ms for long CI watches
outcomeyesOutcome rules — see Outcome rules

Actions

ActionPurposeKey options
pr_createOpen a pull requesttitle, body, base, head, draft
pr_viewInspect PR statenumber or branch
run_listList recent workflow runsworkflow, branch, limit
run_watchWait until CI finishesrun_id or workflow (+ optional branch)
run_viewRun metadata and logsrun_id, log_failed

run_watch

When workflow is set (without run_id), definitively lists the latest matching run and then invokes gh run watch --exit-status. Set a generous timeout_ms for long CI jobs.

Structured data and jq

Gh actions that return JSON populate data on the raw result. Use jq predicates in outcome rules:

outcome:
  success:
    - jq: '.conclusion == "success"'
  failure:
    - exit_code: {neq: 0}

Prerequisites

  • GitHub CLI (gh) on PATH
  • Authenticate: gh auth login or set GH_TOKEN in the environment

Node catalog

Copy-paste fragments from .definitively/nodes/gh.yml (installed via definitively init).

Try it: Wire run_watch after a push node to gate on CI green before a final state.

LLM nodes

nodes:
  llm_fix_lint:
    kind: llm
    model: auto
    prompt_file: .definitively/prompts/fix-lint.md
    timeout_ms: 3600000
    command:
      - cursor-agent
      - agent
      - --force
      - --workspace
      - "."
      - --print
      - --output-format
      - stream-json
      - --
    outcome:
      success:
        - jq: '.status == "ok"'
        - signal: fix_complete
      failure:
        - timeout: true
        - signal: refused

Fields

FieldRequiredDescription
kindyesMust be llm
prompt_fileyesPath to markdown prompt (relative to workspace root)
commandyesargv prefix for the LLM runner; prompt appended after --
modelnoModel hint passed to runner
timeout_msnoSession timeout
outcomeyesOften uses jq on stream JSON and signal predicates

Prompt files

Store prompts under .definitively/prompts/. Reference them by path in prompt_file.

The dev quality loop uses one prompt per fix step (fix-lint.md, fix-test.md, etc.).

Try it: Copy prompts/example.md from templates and wire a minimal LLM node (use stub runner in dev if no agent installed).

Outcome rules

Outcome rules classify raw node results into labels. The engine uses the first matching label, then transitions via the state’s on: map.

Structure

outcome:
  success:
    - exit_code: 0
  failure:
    - exit_code: {neq: 0}
  partial:
    - exit_code: 0

Each label maps to a list of predicates. All predicates in a clause must match (AND). The first matching label wins.

Predicates

PredicateExampleMatches when
exit_code0Exit code equals integer
exit_code{neq: 0}Exit code not equal
timeouttrueSubprocess timed out
signalfix_completeNamed signal is truthy in result
jq'.status == "ok"'JSON field matches (LLM output)

Labels and status

LabelInternal statusTypical use
success:successContinue happy path
failure:failureError or fix loop
partial:partialRecoverable incomplete work
retry:failureExplicit retry transition key

If no label matches, the outcome is unknown and the run may error.

Try it: Run a CLI node with a failing command and confirm the failure transition fires.

Validation errors

Programs are validated at load time before execution. Common errors:

ErrorCause
invalid_initialprogram.initial names a state that does not exist
invalid_transitionon: target names an undefined state
missing_node_refActive state without node:
undefined_nodeActive state references unknown node ID
no_final_stateNo state with type: final
unreachable_finalNo final state reachable from initial

Fixing validation errors

  1. Read the error message — it includes state names and paths.
  2. Ensure every on: target exists in states:.
  3. Ensure active states have valid node: references.
  4. Add at least one final state on a reachable path.

Missing program fields

Loader errors when program lacks id, version, or initial.

Try it: Intentionally break example.yml (bad transition target) and run definitively run to see the error format.

Case study: dev quality loop

This repository dogfoods definitively via .definitively/programs/dev-quality-loop.yml — a workflow that runs moon quality gates and LLM fix loops until the build passes or the run fails.

Overview

stateDiagram-v2
  direction LR
  idle --> lint: start
  lint --> doctor: success
  lint --> fix_lint: failure
  lint --> fix_lint: partial
  fix_lint --> lint: success
  fix_lint --> fix_lint: failure
  fix_lint --> fix_lint: retry
  doctor --> test: success
  test --> coverage: success
  coverage --> docs: success
  docs --> build: success
  build --> commit: success
  commit --> done: success

Each fix_* state runs an LLM node with a dedicated prompt file, then loops back to retry the gate.

CLI gate nodes

Each gate is a moon task:

moon_lint:
  kind: cli
  command: ["moon", "run", "definitively:lint"]
  timeout_ms: 300000
  outcome:
    success:
      - exit_code: 0
    failure:
      - exit_code: {neq: 0}
    partial:
      - exit_code: 0

LLM fix nodes

Fix nodes share a YAML anchor for cursor-agent command and outcome rules:

command: &cursor_agent
  - cursor-agent
  - agent
  - --force
  - --workspace
  - "."
  - --print
  - --output-format
  - stream-json
  - --
outcome: &llm_outcome
  success:
    - jq: '.status == "ok"'
    - signal: fix_complete
  failure:
    - timeout: true
    - signal: refused

Running it

From the definitively repo root (with moon and cursor-agent on PATH):

definitively run "$PWD/.definitively/programs/dev-quality-loop.yml"

Try it: Visualize the full graph — definitively visualize .definitively/programs/dev-quality-loop.yml.

Git and GitHub automation

This pattern chains native git and gh nodes to inspect the repo, commit and push changes, open a PR, and wait for CI — without language-specific tool wrappers.

Overview

stateDiagram-v2
  direction LR
  idle --> check: start
  check --> push: clean
  check --> commit: dirty
  commit --> push: success
  push --> pr: success
  pr --> watch: success
  watch --> done: success
  watch --> failed: failure

Example program

After definitively init, find the full workflow at .definitively/programs/git-gh-ship.yml:

definitively run "$PWD/.definitively/programs/git-gh-ship.yml"

Key nodes

Check working tree

repo_status:
  kind: git
  action: status
  outcome:
    success:
      - signal: clean
    partial:
      - signal: dirty

Route partial to commit when there are uncommitted changes; success skips straight to push.

Commit and push

ship_commit:
  kind: git
  action: commit
  options:
    message: "chore: ship via definitively"
    add: all

push_origin:
  kind: git
  action: push
  options:
    remote: origin
    set_upstream: true

Open PR and watch CI

open_pr:
  kind: gh
  action: pr_create
  options:
    title: "chore: ship via definitively"

watch_ci:
  kind: gh
  action: run_watch
  options:
    workflow: definitively-ci.yml
  timeout_ms: 900000

Adjust workflow to match your repository’s CI workflow filename.

Node catalog

Reusable fragments live in:

  • .definitively/nodes/git.yml
  • .definitively/nodes/gh.yml

Copy nodes into your own programs; each file is a set of documented YAML fragments.

When to use this vs dev quality loop

PatternBest for
Dev quality loopLint/test/fix loops with LLM repair
Git + GitHub automationShip commits, PRs, and CI gates

They compose: run quality gates first, then a git-gh-ship program to publish.

Try it: Visualize the workflow — definitively visualize .definitively/programs/git-gh-ship.yml.

Git hook integration

Git hooks can run definitively gate programs — linear quality checks with no LLM fix loops. This dogfoods the runner on every commit and push while keeping hooks fast and deterministic.

Gate programs vs fix loops

ProgramHookLLMPurpose
pre-commit-gate.ymlpre-commitnoFormat, lint, doctor
pre-push-gate.ymlpre-pushnoFull CI parity + mdBook build
dev-quality-loop.ymlmanualyesGate + AI fix loops + ship

Gate programs use cli nodes only. On failure they transition to a failed final state and the hook exits non-zero.

Pre-commit gate

format → lint → doctor → done

Installed at .definitively/programs/pre-commit-gate.yml (via definitively init template).

Pre-push gate

format → lint → doctor → test → coverage → docs → build → book-build → done

Matches the definitively CI pipeline plus mdBook build.

devenv / prek wiring

In devenv.nix, hooks call definitively directly:

export DEFINITIVELY_WORKSPACE="$DEVENV_ROOT"
definitively run "$DEVENV_ROOT/.definitively/programs/pre-commit-gate.yml"

Requires definitively on PATH (devenv module or DEFINITIVELY_FROM_SOURCE=1).

When to use AI fix loops

Do not wire dev-quality-loop.yml into hooks by default — LLM nodes are slow, costly, and modify files during commit.

Run fix loops manually before committing:

definitively run "$PWD/.definitively/programs/dev-quality-loop.yml"

Try it: Break credo intentionally, run pre-commit-gate.yml, confirm the hook fails at the lint state.

Retry and fix loops

The dev quality loop pattern: gate → fix → gate.

Pattern

lint:
  type: active
  node: moon_lint
  on:
    success: doctor
    failure: fix_lint
    partial: fix_lint

fix_lint:
  type: active
  node: llm_fix_lint
  on:
    success: lint
    failure: fix_lint
    retry: fix_lint
  • failure and partial on the gate route to a fix state
  • success on the fix state returns to the gate
  • retry keeps trying the fix when the LLM session fails

Avoiding infinite loops

Definitively does not cap retries automatically. For production use:

  • Set reasonable timeout_ms on LLM nodes
  • Add a counter state or max-retry final state in your program design
  • Monitor logs at DEFINITIVELY_LOG_LEVEL=INFO

Try it: Simplify the pattern to two states (run → fix → run) before adding the full gate chain.

Approval gates

Some workflows pause for human or external approval before continuing.

Passive and active gates

  • Passive states wait for a labeled transition without running a node.
  • Programs can include states that require an approval label (e.g. done) before proceeding to a final state.

Exit code 2

When a run reaches an approval gate that cannot auto-approve, the CLI exits with code 2 (distinct from failure).

Auto-approve in automation

Tests and CI may supply auto labels programmatically. Design programs with explicit on: maps for approval labels you support.

Try it: Study definitively/test/fixtures/approval_state.yml in the source repo for a minimal approval example.

Visualizing workflows

definitively visualize program.yml                    # DOT + PNG (default)
definitively visualize program.yml --format dot       # DOT only
definitively visualize program.yml --format png       # PNG only (needs dot)
definitively visualize program.yml --format svg       # SVG only (needs dot)
definitively visualize program.yml --out /tmp/myflow  # custom basename

Default output

Without flags, outputs go to:

.definitively/visualizations/<program-basename>.dot
.definitively/visualizations/<program-basename>.png

Paths are printed to stdout.

Graphviz

Install Graphviz for PNG/SVG. The devenv module includes graphviz. DOT-only mode works without dot.

If PNG compilation fails in default mode, DOT is still written and the command exits non-zero with a helpful message.

Try it: Visualize example.yml after init, then open the PNG in your viewer.

Commands and flags

definitively init [--force]
definitively run </full/path/to/program.yml>
definitively visualize </full/path/to/program.yml> [--format dot|png|svg] [--out <basename>]

init

Copies packaged templates into .definitively/ under the workspace.

FlagDescription
--forceOverwrite existing template files

Workspace root: current directory or DEFINITIVELY_WORKSPACE.

run

Executes a program synchronously.

RequirementDetail
Program pathFull path to YAML under .definitively/
Success outputPrints workflow finished
Exit 0Run reached a final state successfully
Exit 1Load error, execution error, or workflow failure
Exit 2Stuck at approval gate without auto label

visualize

Renders program structure as Graphviz. See Visualizing workflows.

Mix task (contributors)

Inside definitively/:

mix definitively run ../.definitively/programs/example.yml

Environment

See Environment variables.

Install channels

All channels install the same escript (Definitively.CLI).

ChannelBest forCommand
curlQuick installSee below
HexElixir developersmix escript.install hex definitively --force
Nix flakeReproducible CInix build github:Industrial/definitively#definitively
devenv moduleNix/devenv teamsImport devenvModules.definitively
HomebrewmacOS/Linux brew usersbrew tap idcleartomwieland/tap && brew install definitively

curl (GitHub releases)

curl -fsSL https://raw.githubusercontent.com/Industrial/definitively/main/install.sh | bash

Pin a version:

curl -fsSL https://raw.githubusercontent.com/Industrial/definitively/main/install.sh | bash -s -- --version definitively-v0.2.0

Tarballs: definitively-<version>-{linux-x86_64,darwin-arm64}.tar.gz

Hex

mix local.hex --force
mix escript.install hex definitively --force
export PATH="$(mix escript.install_path):$PATH"

Requires Elixir ~> 1.18 and Erlang/OTP 27+.

Nix flake

nix build github:Industrial/definitively#definitively
./result/bin/definitively init

From a checkout: nix build .#definitively

devenv module

# devenv.yaml
inputs:
  definitively-repo:
    url: github:Industrial/definitively
    flake: true
# devenv.nix
{ inputs, ... }: {
  imports = [ inputs.definitively-repo.devenvModules.definitively ];
}

Provides definitively and graphviz on PATH.

Homebrew

brew tap idcleartomwieland/tap https://github.com/idcleartomwieland/homebrew-tap
brew install definitively

Runtime dependencies

ToolRequired?Used by
Erlang/OTP 27+Yes (Hex/source)escript interpreter
Graphviz dotOptionalvisualize PNG/SVG
moonOptionalPrograms with moon CLI nodes
cursor-agentOptionalLLM nodes in quality loop
gitOptionalPrograms with git CLI nodes

YAML schema cheat sheet

program:
  id: string          # required
  version: integer    # required
  initial: state_name # required

states:
  <name>:
    type: passive | active | final
    node: <node_id>   # required when type: active
    on:
      <outcome_label>: <next_state>

nodes:
  <id>:
    kind: cli | llm | git | gh
    command: [argv, ...]       # cli, llm
    action: status | commit | pr_create | run_watch | ...
    options: { key: value }    # git, gh
    cwd: path
    timeout_ms: integer
    model: string       # llm
    prompt_file: path   # llm
    outcome:
      success: [predicates]
      failure: [predicates]
      partial: [predicates]
      retry: [predicates]

# Predicates (each list item is one of):
#   exit_code: 0
#   exit_code: {neq: 0}
#   timeout: true
#   signal: name
#   jq: '.field == "value"'

Glossary

TermDefinition
WorkspaceDirectory containing .definitively/; parent of the config folder
ProgramYAML file defining a complete FSM workflow
StateA step in the FSM: passive, active, or final
NodeReusable executor (cli, git, gh, or llm) referenced by active states
ActionNamed operation for git/gh nodes (e.g. commit, run_watch)
OptionsAction-specific parameters on git/gh nodes
SignalNamed boolean flag parsed from node output (e.g. clean, dirty)
DataStructured JSON map from git/gh node output, usable with jq predicates
OutcomeTyped result of a node (success, failure, partial, unknown)
LabelNamed outcome used in transitions (success, failure, …)
TransitionEdge from one state to another, keyed by outcome label
RunSingle execution of a program from initial to final state

Migrating from orchestrator

The product was renamed from orchestrator to definitively in v0.2.0.

BeforeAfter
CLI orchestratordefinitively
.orchestrator/.definitively/
Hex orchestratordefinitively
Tags orchestrator-v*definitively-v*
ORCHESTRATOR_WORKSPACEDEFINITIVELY_WORKSPACE
Moon orchestrator:*definitively:*

Steps

  1. Install definitively v0.2.0+
  2. Rename .orchestrator/.definitively/
  3. Update program YAML paths and any scripts referencing the old CLI name
  4. Update CI/release tags to definitively-v*

Developing definitively

Contributors work from the definitively repository.

Setup

git clone --recurse-submodules git@github.com:Industrial/definitively.git
cd definitively
devenv shell

Set DEFINITIVELY_FROM_SOURCE=1 before devenv shell to build the escript from definitively/ on enter.

Quality pipeline

moon run definitively:lint definitively:doctor definitively:test definitively:coverage definitively:docs definitively:build
TaskChecks
definitively:lintCredo --strict
definitively:doctor@moduledoc / @doc / @spec coverage
definitively:testExUnit
definitively:coverage≥ 90% threshold
definitively:docsExDoc with --warnings-as-errors
definitively:buildFull chain + compile

API docs

cd definitively && mix docs
# → doc/index.html

ExDoc covers modules; this book covers usage.

Book development

mdbook serve book   # live preview at http://localhost:3000
mdbook build book   # output in book/book/

Release maintainers

Push tag definitively-vX.Y.Z matching definitively/mix.exs version.

Workflow release-definitively.yml:

  1. validate — tag/version check, mix test --cover, mix hex.build
  2. build — escript tarballs (linux-x86_64, darwin-arm64)
  3. github-release — attach assets + install.sh
  4. hex-publishmix hex.publish --yes (HEX_API_KEY)
  5. homebrew-tap-bump — update tap (HOMEBREW_TAP_TOKEN)

Re-run without re-tagging: Actions → Release definitively → Run workflow → enter tag.

Required secrets: HEX_API_KEY, HOMEBREW_TAP_TOKEN (optional until Homebrew automation enabled).