Your AI agent builds the Mendix app. mx-codr makes sure it is actually finished.
Claude Code, Codex, Cursor and OpenCode can already write Mendix domain models, microflows and pages. What they don't do on their own is prove the work: a test for every screen, a model that passes Mendix's own checks, microflows a colleague can read, screens that aren't glued together. mx-codr adds exactly that — one installer, and one command that answers DONE or NOT DONE.
It sits on top of mxcli:
- mxcli opens command-line access to a Mendix model
- mx-codr adds a harness — skills, rules and hooks — to turn it into a delivery workflow.
- Test-first, automatically. The agent writes a failing browser test before each feature, and every page and action ends up with one.
- One command decides "done".
bash tests/gate.shruns the browser tests,mx check, lint, test coverage, naming and layout together, in under half a minute. - Rules the agent keeps following. Hooks repeat them on every prompt and after every model change, so they don't fade by the third feature.
- A model people can read. Business captions on every activity, process folders, shared snippets and sub-microflows instead of copies, spacing from the Atlas theme.
- Your agent, your OS. Claude Code, Codex, Cursor and OpenCode, on macOS, Linux and Windows — Windows on ARM included.
Copy the mxcodr/ folder into your Mendix project — or into an empty folder, and the
installer creates the app for you. Then, from that folder:
macOS and Linux
bash mxcodr/install.sh --with-depsWindows — open PowerShell as administrator and run:
powershell -ExecutionPolicy Bypass -File mxcodr\bootstrap.ps1Windows has no bash out of the box, so bootstrap.ps1 first installs Git for Windows
(which brings Git Bash), Python and Node with winget, then runs the same installer.
Administrator rights are needed because winget installs Docker Desktop. Already have
Git Bash? Run bash mxcodr/install.sh --with-deps from Git Bash instead.
Start a new agent session and ask for a feature. That's it.
You don't install the pieces one by one. The installer checks what this machine has, fetches what is missing, and tells you plainly about anything it could not do.
| What the installer does | |
|---|---|
| Your Mendix app | Creates one with mxcli new if the folder has none (Mendix 11.12.1 unless you set MX_VERSION) |
| mxcli | Uses the newest mxcli on the machine, offers the latest release when it is newer, and verifies the download's checksum |
| Docker | Installs it when it is missing. It is optional: see Running without Docker |
| Python, Node, Playwright and its browser | Installs them with --with-deps — the checkers and browser tests run on them |
| MxBuild | Downloads the one for your Mendix version with --with-deps, so mx check runs |
| PostgreSQL | Sets it up when you work without Docker, with --with-deps |
| Skills, lint rules, checkers, hooks | Puts them where each of the four agents looks for them |
| Windows | Applies the junctions and ARM64 fixes that Studio Pro's mxbuild needs |
What cannot be installed unattended — a JDK, a Docker daemon that has to be started if you use one — is listed at the end with the command to run.
you ask ─▶ agent writes a test ─▶ test fails (red) ─▶ agent builds it in MDL
│
DONE ◀── gate: tests · mx check · lint · coverage · naming · layout ◀── test passes
You never run the checks yourself. The agent runs the gate, and the hooks make sure it does.
Two A/B runs: the same prompt, the same model, a fresh app each time — once without mx-codr, once with it.
| Without mx-codr | With mx-codr | |
|---|---|---|
| Browser tests written | 0 | 5–7 |
| Gate at the end | NOT DONE (no tests, no coverage, spacing errors) | DONE |
| First verified DONE | never | after 11.6–12.2 min |
| Whole session | 11.0–11.4 min | 15.2–15.9 min |
About a minute more to reach a verified result; the rest of the extra time was the agent polishing after DONE.
mxcodr/ is the whole bundle. Everything below is about installing and using it.
Nothing in this harness is a tool you operate. There is no Python script to invoke,
no checker to remember the arguments of, no order to run things in. After
install.sh, every piece is found and used by the agent on its own:
| What | How the agent finds it |
|---|---|
| The six rules, in prose | SKILL.md files in the three directories each host looks in |
| The always-loaded reminder | .claude/rules/ and .cursor/rules/, loaded on every turn |
MOD001, REU001 |
mxcli lint discovers .claude/lint-rules/*.star by itself |
check_mdl.py, check_test_coverage.py |
the skills that need them name the exact command; the gate runs them too |
| The gate | host hooks fire it, and the test-first-delivery skill tells the agent to |
The Python checkers exist because two of the rules cannot be expressed as lint
rules — activity captions are not in the model catalog, and test coverage means
reading tests/ off disk. They are an implementation detail of those two rules,
installed at tools/mdl-checks/ so every host can cite one path. The agent calls
them. You never have to.
The same is true of tests/gate.sh. The hooks run it, and the skills tell the agent
to run it before claiming anything is finished. You can run it yourself when you
want to see where a project stands — that is a convenience, not a step.
So the whole of your involvement is, from your project folder:
bash mxcodr/install.sh --with-depsand then working with your agent as usual.
| Rule | What it asks for |
|---|---|
test-first-delivery |
a failing test before the feature, and a test naming every page and ACT_ microflow |
module-structure |
documents in process-named folders, ACT_/SUB_ split, microflows under 15 activities |
naming-and-captions |
PascalCase, ENUM_/SNIPPET_ prefixes, _NewEdit/_View/_Overview pages, a business caption on every activity |
reuse-and-snippets |
a snippet used on more than one page, a SUB_ microflow with more than one caller |
organize-project |
nothing orphaned, nothing left at module root |
spacing-and-layout |
widgets on one line spaced with Atlas design properties, never custom CSS |
| Why | |
|---|---|
| mxcli | everything runs through it |
| Mendix Studio Pro or a cached mxbuild | mx check validates the model |
| PostgreSQL | the app's database, and a separate <project>_test one |
| bash | the harness is shell scripts — Git Bash on Windows |
| Python 3 | for the two checkers the agent calls; you never invoke it |
| Node + playwright-cli | the browser tests |
| A JDK | matching the Mendix version; Studio Pro installs one |
| Docker | installed by default; optional: see Running without Docker |
--with-deps installs the ones that can be installed unattended. It never installs
a JDK — that wants a licence click.
Copy mxcodr/ into your Mendix project and run the installer from the project
folder, one level above mxcodr/ — not from inside mxcodr/:
cd MyApp # the folder with MyApp.mpr and mxcodr/
bash mxcodr/install.sh --with-depscd mxcodr && bash install.sh still installs into the folder above, but then the
target is guessed rather than named, and with no app there it stops to ask.
bash mxcodr/install.sh [path-to-project] [--no-app] [--with-deps]
path-to-project where to install (default: the current directory, or the
parent project when run from inside the bundle)
--no-app never create a Mendix app; require one to be there already
--with-deps install missing prerequisites with this machine's package
manager. Without it they are only reported.
With no .mpr in the target and --with-deps, it creates a Mendix app for you.
There is no bash on Windows until something installs it, so there is a second entry point for that one job:
powershell -ExecutionPolicy Bypass -File mxcodr\bootstrap.ps1 # from the project folderIt installs Git for Windows, Python and Node with winget, then hands over to
bash install.sh --with-deps.
Run it from an elevated terminal. winget's Docker Desktop install asks for
administrator rights, and unelevated it fails with exit code: 4294967291 and is
reported as missing.
If you already have Git Bash, skip bootstrap.ps1 and run bash mxcodr/install.sh from the project folder.
.claude/skills/<name>/ Claude Code
.agents/skills/<name>/ Codex, and other tools on the open SKILL.md standard
.ai-context/skills/<name>/ mxcli, Cursor, OpenCode, Windsurf, Aider
.claude/rules/ the always-loaded rule (Cursor's copy in .cursor/rules/)
.claude/lint-rules/ found by `mxcli lint` with nothing to register
tools/mdl-checks/ the Python checkers the skills cite
tests/ the harness scripts, plus tests/harness.env
.claude/settings.local.json the hooks (Cursor, Codex and OpenCode get their own)
Three copies of the same skills, because each tool looks somewhere different. All of it is discovered — nothing here needs registering, importing or configuring.
The five harness scripts are replaced on every install: a fix in gate.sh that
never reaches an installed project is not a fix. Your own verify-*.test.sh and
credentials.env are never overwritten.
One command, five checks, run concurrently — the browser suite, mx check, mxcli lint, test coverage and naming/captions. Every step runs even when another fails,
so one call reports the whole picture. Exit 0 only when all five pass.
== gate
tests: Total: 12 Passed: 12 Failed: 0 Time: 2m14s
mx check: 0 errors
lint: 59 issues: 0 errors, 24 warnings, 35 info
coverage InvoiceDesk: PASS 14/14 elements covered by 12 test script(s)
naming: PASS 0 failure(s) over 246 lines
DONE — every check passed
The agent runs this. The installed hooks run it too, and refuse to let Codex or Cursor finish a turn while it is red. When you want to look yourself:
bash tests/gate.sh # the done gate
bash tests/gate.sh --boot-if-needed # boot the app first if nothing answers
bash tests/gate.sh --only <feature> # one test, warm browser, red loop
bash tests/orient.sh # what is in this project
bash tests/diagnose.sh # why is the app not answeringtests/harness.env is written by the installer and read by every harness script.
The environment still wins, so any of it can be overridden for one run.
| Key | What it is |
|---|---|
MDL_MXBUILD_PATH |
the Studio Pro or cached mxbuild mx check runs |
MDL_DB_HOST / _NAME / _USER / _PASSWORD |
the database |
JAVA_HOME |
a JDK on a path with no spaces (see below) |
MDL_BOOT_COMMAND |
how the gate boots the app when nothing answers |
Four things stand between a Windows machine and a running Mendix app, and none of them reports itself usefully. The installer fixes three, without being asked.
| Symptom if unfixed | |
|---|---|
| A JDK on a path with spaces | mxbuild splits its own command line, so C:\Program Files (Arm)\zulu21 arrives as four unrecognised arguments and it exits printing usage |
| No Gradle in the mxbuild cache | No supported Gradle installation found, raised after mxbuild is already answering, so it reads as a model problem |
ARM Studio Pro ships win-arm64 tools only |
mxbuild launches win-x64 and dies with Win32Exception (2) before it listens |
Junctions, so nothing is copied and no administrator rights are needed.
The fourth cannot be fixed from outside mxcli. Its liveness probe is
os.Process.Signal(0), and Windows rejects every signal except Kill — so a
perfectly healthy mxbuild and a perfectly healthy runtime both read as "exited
during startup" on the first poll. This is not an ARM quirk; no Windows machine
can boot an app with mxcli run --local.
The harness works around it for booting: the installer writes
MDL_BOOT_COMMAND="bash tests/run-app.sh", which drives mxbuild and the standalone
runtime over the M2EE admin API instead. gate.sh --boot-if-needed then works.
There is no equivalent for mxcli test --local, which boots the app itself.
Microflow tests need a patched mxcli on Windows until the fix is upstream.
Ports. Studio Pro running an app holds 8080 and 8090. The harness defaults to
8081, which still collides on the admin port. Pass APP_PORT / ADMIN_PORT if you
are running both at once.
Screenshots need playwright (the npm package), not playwright-cli (the
session tool mxcli playwright drives). Having only the second is what makes
Playwright look installed while mxcli run --local --screenshot does nothing. Its
Chromium is a separate download, pinned per package, so one tool's browser does not
satisfy the other.
mxcli test points the project's after-startup microflow at its own injected
MxTest.RegisterEndpoint while tests run. A run that dies before cleanup leaves it
there, overwriting whatever the app had — with no record of the old value. The next
run then fails somewhere else entirely, because it reads the leftover as your
setting.
./mxcli -p <app>.mpr -c 'SHOW SETTINGS' # look at AfterStartup
./mxcli -p <app>.mpr -c "ALTER SETTINGS MODEL AfterStartupMicroflow = 'Module.Microflow'"mxcli does warn that the project was left modified. It does not say what the value was, so note it before running tests against a project you care about.
mxcodr/ is a copy of files that live in the harness repo — mxcodr/README.md has the
table of which file comes from where. Edit it there, not here.