Run Extensions
divekit run executes local extensions against repositories from a distribution. Typical uses are static analysis, report extraction, custom checks, or orchestration around external tools.
Before running an extension, make repositories available locally:
divekit fetch -d ST2M4
Then run an extension from .divekit/scripts/run/ or by path:
divekit run analyze.lua -d ST2M4
divekit run ./scripts/analyze.py -d ST2M4
Use --fetch-first when Divekit should fetch before executing:
divekit run analyze.lua -d ST2M4 --fetch-first
Simple Lua Extensions
A simple Lua extension returns a function. Divekit calls that function once for every selected repository.
return function(dk)
local pom = dk.fs.read("pom.xml")
dk.results.emit({
has_pom = pom ~= nil,
})
end
During per-repository execution, dk.run contains the current repository context:
| Field | Meaning |
|---|---|
dk.run.distribution | Current distribution name. |
dk.run.uuid | Current repository UUID. |
dk.run.path | Local path to the repository. |
dk.run.result_file | Per-repository result file path. |
dk.run.results_dir | Result directory for the full run. |
Lua Workflows
Use a Lua workflow when an extension needs lifecycle steps around the per-repository analysis. A workflow registers callbacks at top level:
dk.run.prepare(function(ctx)
os.execute("docker compose up -d --wait")
end)
dk.run.each(function(repo)
local pom = dk.fs.read("pom.xml")
dk.results.emit({
uuid = repo.uuid,
has_pom = pom ~= nil,
})
end)
dk.run.cleanup(function(ctx)
os.execute("docker compose down")
end)
Run it like any other Lua extension:
divekit run workflow.lua -d ST2M4
Workflow order:
dk.run.prepare(function(ctx) ... end)runs once before repository processing.dk.run.each(function(repo) ... end)runs once per selected repository.dk.run.cleanup(function(ctx) ... end)runs once at the end, including after prepare or per-repository failures.
The workflow context passed to prepare and cleanup contains:
| Field | Meaning |
|---|---|
ctx.distribution | Current distribution name. |
ctx.results_dir | Result directory for the full run. |
ctx.repositories | List of selected repositories with uuid, path, source, and label. |
ctx.repos | Alias for ctx.repositories. |
Lua extensions executed by divekit run are local code execution. They may use standard Lua APIs such as os.execute, which makes workflows suitable for starting and stopping local services such as Docker Compose.
Divekit waits for prepare to finish before running repository analysis; use blocking readiness commands such as docker compose up -d --wait when services must be ready before each starts.
External Extensions
Shell, Python, Node, Deno, Go, Java, and Kotlin extensions run once per repository by default. Divekit passes repository context through stdin JSON and environment variables:
echo "$DIVEKIT_REPO_UUID"
echo "$DIVEKIT_REPO_PATH"
echo "$DIVEKIT_RESULTS_DIR"
Use --js-runner to choose the JavaScript runner and --runner-arg to pass runner-specific flags:
divekit run ./scripts/analyze.js -d ST2M4 --js-runner bun
divekit run ./scripts/analyze.ts -d ST2M4 --runner-arg=--allow-read --runner-arg=--allow-env
External Workflows
Use run init to create a starter workflow extension:
divekit run init
The interactive form asks for the workflow name and language. You can also pass both values directly:
divekit run init has_readme --language lua
Lua is the recommended default and creates a normal named Lua workflow:
.divekit/scripts/run/has_readme.lua
Non-Lua languages create workflow directories:
.divekit/scripts/run/has_readme/
workflow.json
workflow.py
Use --language lua|shell|python|javascript|typescript|go|java|kotlin to choose the starter runtime. JavaScript and TypeScript workflows can also set a preferred runner:
divekit run init used_ai --language javascript --runner bun
Use -y to accept defaults (repository_check, lua) and --force when generated files should be overwritten.
The installed examples also include workflow directories for Shell, JavaScript, TypeScript, Go, Java, and Kotlin. Workflow directories can contain normal project files such as package.json, node_modules, go.mod, or helper modules next to the entry file.
The manifest describes the workflow and selects the runtime. Entry files and callback names have defaults, so most workflows only need metadata and a language:
{
"version": 1,
"name": "has_readme",
"description": "Checks whether each repository contains a README.md file.",
"author": {
"name": "Divekit"
},
"runtime": {
"language": "python"
}
}
schema is optional. When used, point it at the Divekit CLI project, for example:
{
"schema": "https://gitlab.git.nrw/divekit/divekit-cli/-/raw/main/assets/schemas/workflow.v1.json",
"version": 1,
"name": "has_readme",
"runtime": {
"language": "python"
}
}
Default workflow entries:
| Language | Default entry | Default functions |
|---|---|---|
shell | workflow.sh | prepare, each, cleanup |
python | workflow.py | prepare, each, cleanup |
javascript | workflow.js | prepare, each, cleanup |
typescript | workflow.ts | prepare, each, cleanup |
go | . | Prepare, Each, Cleanup |
java | Workflow.java | prepare, each, cleanup |
kotlin | workflow.kts | prepare, each, cleanup |
Override defaults only when the workflow uses different names:
{
"version": 1,
"name": "custom_check",
"runtime": {
"language": "javascript",
"runner": "bun",
"args": ["--smol"]
},
"workflow": {
"entry": "src/check.js",
"functions": {
"each": "analyzeRepository"
}
}
}
Divekit calls workflow functions in order:
prepare(ctx)once before repository processing, when defined.each(repo)once per selected repository.cleanup(ctx)once at the end, including after prepare or per-repository failures, when defined.
Workflow functions can return a result directly. JavaScript and TypeScript workflows may also return a Promise; Divekit waits for it.
Divekit waits for prepare to finish before running each; use blocking readiness commands such as docker compose up -d --wait when services must be ready.
Example workflow.py:
import os
def prepare(ctx):
print(f"preparing analysis for {len(ctx['repositories'])} repositories")
def each(repo):
return {
"uuid": repo["uuid"],
"has_readme": os.path.exists(os.path.join(repo["path"], "README.md")),
}
def cleanup(ctx):
print("cleanup finished")
Run the workflow directory:
divekit run has_readme -d ST2M4
Divekit recognizes workflow directories by workflow.json. It executes the workflow with the directory as the working directory, so relative includes/imports and local dependencies stay predictable.
Results
Lua extensions can emit structured rows through dk.results.emit(...). External extensions can print JSON lines to stdout. Divekit collects emitted rows in the run results directory and can print a summary:
divekit run analyze.lua -d ST2M4 --output-format table
divekit run analyze.lua -d ST2M4 --output-format json,csv,md
divekit run analyze.lua -d ST2M4 --output-format csv --output-format md
Divekit always writes results.ndjson while the run is active and results.json after aggregation. Additional selected formats are written next to them:
| Format | File | Timing |
|---|---|---|
ndjson | results.ndjson | Live during the run. |
json | results.json | End of run. |
csv | results.csv | End of run. |
md | results.md | End of run. |
table | results.txt | End of run. |
Use --output-dir to write result files to a custom directory:
divekit run analyze.lua -d ST2M4 --output-dir ./analysis-results --output-format csv,md
Related pages: