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
| Part | Topics |
|---|---|
| I | Install, first run, core concepts |
| II | Workspace layout and scaffolding |
| III | YAML program authoring |
| IV | Real-world patterns |
| V | CLI reference |
| VI | All install channels |
| Appendices | Cheat 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 — states, nodes, outcomes
- Program structure — YAML reference
- Case study: dev quality loop — a real multi-step program
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:
| Kind | Runs |
|---|---|
cli | A shell command (argv list) |
git | Structured git operations (status, commit, push, …) |
gh | GitHub CLI operations (pr_create, run_watch, …) |
llm | An 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
- Program paths must be under
.definitively/—Definitively.Workspaceresolves layout from the program file location. - Prompt paths in LLM nodes are relative to the workspace root (e.g.
.definitively/prompts/fix-lint.md). - 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
| Variable | Default | Purpose |
|---|---|---|
DEFINITIVELY_WORKSPACE | Parent of .definitively/ from program path | Workspace root for runs, init, and visualize |
DEFINITIVELY_LOG_LEVEL | INFO | Logging verbosity: TRACE, DEBUG, INFO, WARNING, ERROR |
DEFINITIVELY_LLM_COMMAND | Stub in dev | Override default LLM runner (space-separated argv prefix) |
DEFINITIVELY_FROM_SOURCE | unset | When 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
| Path | Purpose |
|---|---|
programs/example.yml | Minimal passive → active → final workflow |
prompts/example.md | Sample LLM prompt |
env.example | Environment variable hints |
visualizations/.gitkeep | Ensures 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
| Field | Required | Description |
|---|---|---|
id | yes | Stable identifier (atom in Elixir) |
version | yes | Integer schema version |
initial | yes | Name 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
| Type | Behavior |
|---|---|
passive | Waits for an external transition label (e.g. approval) |
active | Runs the referenced node when entered |
final | Terminal 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 pathfailure— hard failurepartial— 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.initialmust name an existing state.- At least one
type: finalstate 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
| Field | Required | Description |
|---|---|---|
kind | yes | Must be cli |
command | yes | argv list (no shell unless you invoke sh -c) |
timeout_ms | no | Kill subprocess after this many milliseconds |
cwd | no | Working directory (default: workspace root) |
outcome | yes | Outcome rules — see Outcome rules |
Tips
- Prefer argv lists over shell strings for clarity and safety.
- Set
timeout_mson long-running commands (tests, builds). - Map both
failureandpartialwhen 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
| Field | Required | Description |
|---|---|---|
kind | yes | Must be git |
action | yes | Git operation (see table below) |
options | no | Action-specific parameters |
cwd | no | Working directory (default: workspace root) |
timeout_ms | no | Subprocess timeout |
outcome | yes | Outcome rules — see Outcome rules |
Actions
| Action | Purpose | Options | Signals / data |
|---|---|---|---|
status | Working tree snapshot | — | clean, dirty, ahead, behind |
diff | Show changes | staged, stat | has_changes |
add | Stage files | all: true or paths: [...] | exit code |
commit | Create commit | message, add, amend, allow_empty | exit code |
push | Push refs | remote, branch, tags, set_upstream | exit code |
tag | Create tag | name, message, annotate, push | exit code |
The commit action accepts add: all or add: [paths] to stage before committing in one node.
Prerequisites
giton PATH- For
commit/tag: configureuser.nameanduser.emailin 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
| Field | Required | Description |
|---|---|---|
kind | yes | Must be gh |
action | yes | GitHub CLI operation (see table below) |
options | no | Action-specific parameters |
timeout_ms | no | Default 900000 ms for long CI watches |
outcome | yes | Outcome rules — see Outcome rules |
Actions
| Action | Purpose | Key options |
|---|---|---|
pr_create | Open a pull request | title, body, base, head, draft |
pr_view | Inspect PR state | number or branch |
run_list | List recent workflow runs | workflow, branch, limit |
run_watch | Wait until CI finishes | run_id or workflow (+ optional branch) |
run_view | Run metadata and logs | run_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 loginor setGH_TOKENin 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
| Field | Required | Description |
|---|---|---|
kind | yes | Must be llm |
prompt_file | yes | Path to markdown prompt (relative to workspace root) |
command | yes | argv prefix for the LLM runner; prompt appended after -- |
model | no | Model hint passed to runner |
timeout_ms | no | Session timeout |
outcome | yes | Often 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
| Predicate | Example | Matches when |
|---|---|---|
exit_code | 0 | Exit code equals integer |
exit_code | {neq: 0} | Exit code not equal |
timeout | true | Subprocess timed out |
signal | fix_complete | Named signal is truthy in result |
jq | '.status == "ok"' | JSON field matches (LLM output) |
Labels and status
| Label | Internal status | Typical use |
|---|---|---|
success | :success | Continue happy path |
failure | :failure | Error or fix loop |
partial | :partial | Recoverable incomplete work |
retry | :failure | Explicit 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:
| Error | Cause |
|---|---|
invalid_initial | program.initial names a state that does not exist |
invalid_transition | on: target names an undefined state |
missing_node_ref | Active state without node: |
undefined_node | Active state references unknown node ID |
no_final_state | No state with type: final |
unreachable_final | No final state reachable from initial |
Fixing validation errors
- Read the error message — it includes state names and paths.
- Ensure every
on:target exists instates:. - Ensure active states have valid
node:references. - Add at least one
finalstate 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
| Pattern | Best for |
|---|---|
| Dev quality loop | Lint/test/fix loops with LLM repair |
| Git + GitHub automation | Ship 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
| Program | Hook | LLM | Purpose |
|---|---|---|---|
pre-commit-gate.yml | pre-commit | no | Format, lint, doctor |
pre-push-gate.yml | pre-push | no | Full CI parity + mdBook build |
dev-quality-loop.yml | manual | yes | Gate + 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_mson 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.
| Flag | Description |
|---|---|
--force | Overwrite existing template files |
Workspace root: current directory or DEFINITIVELY_WORKSPACE.
run
Executes a program synchronously.
| Requirement | Detail |
|---|---|
| Program path | Full path to YAML under .definitively/ |
| Success output | Prints workflow finished |
| Exit 0 | Run reached a final state successfully |
| Exit 1 | Load error, execution error, or workflow failure |
| Exit 2 | Stuck 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
Install channels
All channels install the same escript (Definitively.CLI).
| Channel | Best for | Command |
|---|---|---|
| curl | Quick install | See below |
| Hex | Elixir developers | mix escript.install hex definitively --force |
| Nix flake | Reproducible CI | nix build github:Industrial/definitively#definitively |
| devenv module | Nix/devenv teams | Import devenvModules.definitively |
| Homebrew | macOS/Linux brew users | brew 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
| Tool | Required? | Used by |
|---|---|---|
| Erlang/OTP 27+ | Yes (Hex/source) | escript interpreter |
Graphviz dot | Optional | visualize PNG/SVG |
| moon | Optional | Programs with moon CLI nodes |
| cursor-agent | Optional | LLM nodes in quality loop |
| git | Optional | Programs 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
| Term | Definition |
|---|---|
| Workspace | Directory containing .definitively/; parent of the config folder |
| Program | YAML file defining a complete FSM workflow |
| State | A step in the FSM: passive, active, or final |
| Node | Reusable executor (cli, git, gh, or llm) referenced by active states |
| Action | Named operation for git/gh nodes (e.g. commit, run_watch) |
| Options | Action-specific parameters on git/gh nodes |
| Signal | Named boolean flag parsed from node output (e.g. clean, dirty) |
| Data | Structured JSON map from git/gh node output, usable with jq predicates |
| Outcome | Typed result of a node (success, failure, partial, unknown) |
| Label | Named outcome used in transitions (success, failure, …) |
| Transition | Edge from one state to another, keyed by outcome label |
| Run | Single execution of a program from initial to final state |
Migrating from orchestrator
The product was renamed from orchestrator to definitively in v0.2.0.
| Before | After |
|---|---|
CLI orchestrator | definitively |
.orchestrator/ | .definitively/ |
Hex orchestrator | definitively |
Tags orchestrator-v* | definitively-v* |
ORCHESTRATOR_WORKSPACE | DEFINITIVELY_WORKSPACE |
Moon orchestrator:* | definitively:* |
Steps
- Install definitively v0.2.0+
- Rename
.orchestrator/→.definitively/ - Update program YAML paths and any scripts referencing the old CLI name
- 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
| Task | Checks |
|---|---|
definitively:lint | Credo --strict |
definitively:doctor | @moduledoc / @doc / @spec coverage |
definitively:test | ExUnit |
definitively:coverage | ≥ 90% threshold |
definitively:docs | ExDoc with --warnings-as-errors |
definitively:build | Full 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:
- validate — tag/version check,
mix test --cover,mix hex.build - build — escript tarballs (linux-x86_64, darwin-arm64)
- github-release — attach assets +
install.sh - hex-publish —
mix hex.publish --yes(HEX_API_KEY) - 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).