Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m

## Unreleased

- fix(doctor, preflight, platform-link): **a clean `fix property-sets --dry-run` on Mendix below 10.24.24 is no longer reported as "every unit has the property names Studio Pro writes".** mxcli's property table starts at 10.24.24, so on 10.24.18 (mxcli v0.27.0) the dry-run printed "Every unit already has the property names Studio Pro writes" while Mendix's `mx diff` exited 129 on a unit mxcli had written (mendixlabs/mxcli#1595). `doctor.sh --property-sets`, `--if-stale` and `--quick` now WARN "NOT PROOF" and name `mx diff <base>.mpr <mine>.mpr out.json` (exit 0) as the proof; the receipt records `unproven`; exit stays 0. `learned-detection-gaps.md` and `platform-link.md` said v0.26.0 closed the gap. Fixture P2b (11 cases). Baseline-routed, +18 words. — saingge
- fix(progress-report, build-plan-status): **HARNESS rows are no longer counted as built rows, and a module's `verify-module.sh` row shows once, as that module's sign-off status on its phase card, instead of as a row.** A HARNESS step is a module's sign-off, not a build, so it is out of every "x of y built" count: the phase cards, the plan rollup and the Stage 5 card in `progress-report.html`, and the lede of `build-plan-status.sh --html` ("N build rows", plus a "Not counted: N HARNESS rows" note). The verify row's evidence is read exactly as #501 reads it (`harness_verify`'s rule, now `verify_evidence` + `module_status` in `progress-report.py`; nothing new measured, no extra script run, no mxcli or mxbuild call) and shown as one line per module: `Verified ✓ <date> (LOOK n/n)`, `Verify owed - LOOK + CONFIRM`, `Verify stale (N builds since)` (or `(a script was marked done- after it)`), `Verify not run`, `built? no verify summary`, `Verify not clean <date> (<why>)` for a summary with a FAULT, FINDING or SKIPPED row, or `Verify clean <date>, LOOK n/n - held: State says <State>` when State is blocked or pending a person (held is not signed off, as #501 never promoted it); a State cell that disagrees is flagged under it, the rollup says how many modules are verified, and the Stage 5 card, KPI and owed text add ", k of n modules signed off". The Phase state badge is read from the build rows only (a phase of HARNESS rows alone says "sign-off only" and its card "Sign-off phase: no build rows"). A module with no verify row shows no status. Other HARNESS rows (a coherence pass, a harness run) stay in the row list, marked "(not counted)". `build-plan.md`, `build-plan.json` and the text output are unchanged, so agents still see every HARNESS step. **Behaviour change to check in your project:** the progress report's built counts drop the HARNESS rows (a plan of 10 rows with 2 HARNESS reads "x of 8"), and the verify rows leave the row tables. Fixture: `test-progress-harness-verify.sh` (H1-H10, N1-N6, both layouts). — MendixMau (decision 2026-10-21)
- fix(exec): **a script restated from a stale DESCRIBE now WARNs instead of silently reverting an earlier exec** (#261). A script drafted from a DESCRIBE taken before an earlier script of the batch ran put four old messages back, with check, mxbuild and lint green. New `project-bin/describe-baseline.sh stamp` writes `-- baseline: <Doc> sha256=<hash>` (plus `-- baseline-mxcli: <version>`) from a DESCRIBE at draft time; `exec.sh` runs `verify` only for a script that carries the header, so other scripts cost one grep and no mxcli call. It reports DRIFT with the first lines of the diff, STALEHEADER, and RESTAMP after an mxcli upgrade (DESCRIBE text may change, so that is not called drift). A re-run by the script's own last recorded write (the #269 ledger) and a fresh model are not drift. Nothing is refused and the exit codes do not change. Preflight STOP 21 gains one clause (`-- supersedes:`, `stamp`, exec.sh only warns) and its trigger names `create or modify` (+24 words). Not-found DESCRIBE and `--version` output are captured from mxcli v0.26.0. Lands after the #269 PR; with it, supersedes draft #311. **Behaviour change to check in your project:** `exec.sh` output can carry `WARN: stale draft: …` lines, and the BUILD-LOG detail cell `WARN stale baseline: <docs>`; `MXTK_RESTATE_GUARD=off` skips the check. — AlvinnYeo (#261)
- fix(exec): **re-running an older `mdlsource/` script over a newer owner's live work now WARNs instead of passing silently** (#269). Three field apps had 99/579, 130/357 and 38/159 documents created by more than one script, and a replay of an older one reverted the newer with exit 0 and every gate green. New `project-bin/script-owners.sh` maps each microflow, nanoflow, page and snippet to the scripts that create or restate it, in run order (digit runs compared as numbers, so `2-core/` runs before `11-fixes/`; `done-` ignored), and reads `-- supersedes:` / `-- superseded-by:` headers. `exec.sh` records every kept write in a machine-local last-writer ledger (`.claude/loop/script-owners/`, self-gitignored, compacted per write); `restore-mpr.sh` marks it on a manual restore. A replay WARNs only when the newer owner's write is live: ledger first, else one DESCRIBE per document, at most 5 per exec (the rest print `CONFLICT (not checked)`). A new owner without a supersedes header gets one `NOTE:` line on its first run, kept out of BUILD-LOG. Nothing is refused and the exit codes do not change. With no co-owner, `exec.sh` runs one grep over `mdlsource/` and no mxcli call. `gate-check.sh` prints the co-owned count when it is above 0. Part of the rebuilt draft #311 (with the #261 PR). **Behaviour change to check in your project:** `exec.sh` output can carry `WARN: restatement: …` and `NOTE: new owner of …` lines; the BUILD-LOG detail cell can carry `WARN reverts: <docs>`. Set `MXTK_RESTATE_GUARD=off` to skip the check. — MendixMau (#269)
Expand Down
41 changes: 36 additions & 5 deletions bin/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ probe_runs() {
# The result is recorded in <project>/.claude/.property-sets-receipt (machine-local, like the
# doctor receipt): line 1 "<date> <time> <result> <detail>", line 2 "mxcli: <--version line>".
# Line 2 is what --if-stale and --quick compare: the check is owed again when mxcli changes.
# Only a settled answer is recorded — clean, needs, failed, or unsupported (the support probe
# Only a settled answer is recorded — clean, unproven, needs, failed, or unsupported (the support probe
# ran and the subcommand is absent). An error or a timeout is never written, and --if-stale
# skips the run only on a recorded clean/unsupported, so a one-off failure (locked model,
# skips the run only on a recorded clean/unproven/unsupported, so a one-off failure (locked model,
# unreadable version, a probe that crashed) is re-checked on the next sync instead of being
# repeated from the receipt until mxcli changes.
#
Expand Down Expand Up @@ -254,8 +254,28 @@ ps_classify() {
PS_DETAIL="exit $1: $(printf '%s\n' "$2" | grep -v -e '^[[:space:]]*$' -e '^WARNING: This is a' | head -1)"
fi
}
# ps_below_floor <version> — 0 when the Mendix version is older than 10.24.24, where mxcli's
# property table starts (mendixlabs/mxcli#1595), so a clean dry-run there proves nothing. 1 when
# it is not older, or the version is not a plain a.b.c[.d].
# Deliberately not gated on the mxcli version: a later release that still has the defect must not
# go silently green. Retire this check (and the warning) when mendixlabs/mxcli#1595 is fixed.
ps_below_floor() {
case "$1" in [0-9]*.[0-9]*.[0-9]*) ;; *) return 1 ;; esac
_pf_a="${1%%.*}"; _pf_r="${1#*.}"; _pf_b="${_pf_r%%.*}"; _pf_r="${_pf_r#*.}"; _pf_c="${_pf_r%%.*}"
case "$_pf_a$_pf_b$_pf_c" in *[!0-9]*) return 1 ;; esac
[ "$_pf_a" -lt 10 ] && return 0
[ "$_pf_a" -gt 10 ] && return 1
[ "$_pf_b" -lt 24 ] && return 0
[ "$_pf_b" -gt 24 ] && return 1
[ "$_pf_c" -lt 24 ]
}
# ps_unproven_warn <detail> <version> — the warning for a clean dry-run below the floor.
ps_unproven_warn() {
warn "property sets: $1 — NOT PROOF: this model (Mendix ${2:-?}) is below 10.24.24, where mxcli's property table starts, so units mxcli writes can still lack keys (mendixlabs/mxcli#1595)"
note "the proof is Mendix's own: mx diff <base>.mpr <mine>.mpr out.json with this version's mx; exit 0 is clean."
}
ps_write_receipt() {
case "$PS_RESULT" in clean|needs|failed|unsupported) ;; *) return 0 ;; esac
case "$PS_RESULT" in clean|unproven|needs|failed|unsupported) ;; *) return 0 ;; esac
[ -n "$PS_VER" ] || return 0
[ -n "$PROJECT_DIR" ] && [ -d "$PROJECT_DIR" ] || return 0
mkdir -p "$PROJECT_DIR/.claude" 2>/dev/null || return 0
Expand Down Expand Up @@ -300,7 +320,13 @@ ps_check() {
fi
ps_classify "$_psk_rc" "$PS_OUT"
case "$PS_RESULT" in
clean) ok "property sets: $PS_DETAIL — every unit has the property names Studio Pro writes" ;;
clean)
_psk_mv="$(mxtk_model_version "$MPR" 2>/dev/null || true)"
if ps_below_floor "$_psk_mv"; then
PS_RESULT=unproven; ps_unproven_warn "$PS_DETAIL" "$_psk_mv"
else
ok "property sets: $PS_DETAIL — every unit has the property names Studio Pro writes"
fi ;;
needs|failed)
warn "property sets: $PS_DETAIL — Studio Pro's merge/diff can throw on these"
printf '%s\n' "$PS_OUT" | grep -E '^ (NEEDS|FAILED) ' | head -10 | while IFS= read -r l; do note "$l"; done
Expand Down Expand Up @@ -335,7 +361,7 @@ ps_apply() {
ps_check
}
ps_exit_code() {
case "$PS_RESULT" in clean) echo 0 ;; not-run) echo 2 ;; unsupported) echo 3 ;; needs|failed) echo 1 ;; *) echo 4 ;; esac
case "$PS_RESULT" in clean|unproven) echo 0 ;; not-run) echo 2 ;; unsupported) echo 3 ;; needs|failed) echo 1 ;; *) echo 4 ;; esac
}

# --property-sets / --fix-property-sets: only this check, then exit. Stands alone so
Expand Down Expand Up @@ -374,6 +400,9 @@ if [ -n "$PS_MODE" ]; then
clean|unsupported)
ok "already checked for $PS_VER: $(sed -n 1p "$(ps_receipt)")"
exit "$(ps_exit_code)" ;;
unproven)
ps_unproven_warn "already checked for $PS_VER: $(sed -n 1p "$(ps_receipt)")" "$(mxtk_model_version "$MPR" 2>/dev/null || true)"
exit "$(ps_exit_code)" ;;
esac
fi
fi
Expand Down Expand Up @@ -1302,6 +1331,8 @@ if [ -n "$PROJECT_DIR" ]; then
_psq="$(sed -n 1p "$(ps_receipt)")"
case "$_psq" in
*" clean "*|*" clean") ok "property sets: last check for this mxcli was clean ($_psq)" ;;
*" unproven "*) warn "property sets: last check for this mxcli was clean, but the model is below Mendix 10.24.24, so that proves nothing (mendixlabs/mxcli#1595)"
note "run: mx diff <base>.mpr <mine>.mpr out.json; exit 0 is clean" ;;
*" unsupported "*) note "property sets: this mxcli cannot check them (v0.26.0+ needed)" ;;
*) warn "property sets: last check for this mxcli: $_psq"
note "repair: bin/doctor.sh --fix-property-sets $PROJECT_DIR" ;;
Expand Down
2 changes: 1 addition & 1 deletion skills/learned-detection-gaps.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Every row was found in the field: **a green result only certifies what its rung
| A pluggable **combo box filter with `lazyLoading: true`** whose options data source also sets the **preset value** | rungs 1–4, lint, and every scripted check — the model is valid and the list *is* filtered | rung 6, by eye, **before touching the filter**: the list is already narrowed by the preset while the filter looks empty (no value, no clear button) until its menu is first opened | Lazy loading defers the options source, so the preset it sets is not rendered until first open. Turn lazy loading off for short lists (≤10 options). Before the build, check every pluggable property that changes *when* data loads (lazy loading, load-on-open), on any widget that should show a value on page load | field run (jopterhorst), 2026-10, [#299](https://github.com/MendixMau/mxcli-project-toolkit/issues/299) |
| A widget **inserted into an existing container** whose theme pins `height` on **positional row classes** (`widget-row-N` and kin) | rungs 1–4, lint, and every scripted check — the MDL insert is correct and the widget renders | rung 6, by eye: the next row overlaps the inserted widget and covers part of it (here the filter's clear button) | The theme sized rows by position, not content, so a taller row 1 runs under row 2. Before an insert into an existing container, grep the theme SCSS for fixed `height` on that container's row/position classes; if found, add a `:has(<new widget>)` rule adjusting both rows | field run (jopterhorst), 2026-10, [#299](https://github.com/MendixMau/mxcli-project-toolkit/issues/299) |
| A **`Class` (or `Style`) on a `SNIPPETCALL`** — `alter page … set (Class: 'x') on <snippetcall>` | rungs 1–4, lint, **and DESCRIBE**, which shows the class on the call | rung 6, a DOM probe: `document.querySelector('.x')` is null on every page that carries it | A snippet call renders **no element of its own**; the snippet's widgets are inlined into the parent, so the class has nothing to sit on. Put the class on the container around the call, or on the snippet's own top-level container. Found when a stepper marker on 6 snippet calls changed nothing on screen with every gate green | field run, mxcli v0.26.0, 2026-10-14, [#336](https://github.com/MendixMau/mxcli-project-toolkit/issues/336) |
| Units **mxcli < v0.26** wrote | rungs 1–6 | Studio Pro merge/diff: *"… do not have the same properties"* | after mxcli upgrades, before merging: `bin/doctor.sh --property-sets <project>`; `platform-link.md` | #295 |
| Units **mxcli < v0.26** wrote, and any mxcli's on **Mendix below 10.24.24** | rungs 1–6 | Studio Pro merge/diff: *"… do not have the same properties"* | after mxcli upgrades, before merging: `bin/doctor.sh --property-sets <project>`; `platform-link.md`. Below 10.24.24 "clean" proves nothing; `mx diff` exit 0 does | #295, mxcli#1595 |

## Operating rules that fall out of this table

Expand Down
6 changes: 5 additions & 1 deletion skills/platform-link.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,11 @@ runtime all pass. Studio Pro's merge and diff then throw — *"Objects with ID
same properties"* — once Studio Pro has saved such a document, and the Version Control panel
fails. Field case: 78 changed units written with v0.24 on a production app, with no gate able to
see them. mxcli v0.26.0 completes the set on every unit it writes, but only on units it rewrites,
so a project upgraded mid-flight still carries the old ones until the repair runs.
so a project upgraded mid-flight still carries the old ones until the repair runs. **Not on
Mendix 10.24 below 10.24.24, through mxcli v0.27.0:** its property table starts at 10.24.24, so on
10.24.18 a unit it writes can still lack keys, and `fix property-sets --dry-run` (hence
`doctor --property-sets`) calls every unit complete (mendixlabs/mxcli#1595). There, the proof is
Mendix's own `mx diff <base>.mpr <mine>.mpr out.json` against the base branch: exit 0 is clean.
`sync-project.sh` runs the read-only check after every toolkit pull until it comes back clean
for the installed mxcli. After that it runs again only when mxcli changes. Errors and timeouts
are never recorded, so the next run re-checks. `doctor --quick`, which exec.sh runs when the mxcli
Expand Down
30 changes: 30 additions & 0 deletions tests/wave2/test-property-sets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,36 @@ grep -qx '/.claude/.property-sets-receipt' "$P/.gitignore" 2>/dev/null && ok "re
printf '%s' "$OUT" | grep -q "added /.claude/.property-sets-receipt to the project's .gitignore" && ok "the .gitignore append is announced" || bad "silent .gitignore append" "$OUT"
grep -q -- 'property-sets -p .*/App.mpr --dry-run' "$P/.stub-log" && ok "the check is the --dry-run" || bad "dry-run not used" "$(cat "$P/.stub-log")"

echo "== P2b a clean dry-run below Mendix 10.24.24 is not proof (mendixlabs/mxcli#1595) =="
setver() { # <project> <version> — give the stub project's .mpr a readable _ProductVersion
rm -f "$1/App.mpr"
python3 -c 'import sqlite3,sys; c=sqlite3.connect(sys.argv[1]); c.execute("create table _MetaData (_ProductVersion text)"); c.execute("insert into _MetaData values (?)",(sys.argv[2],)); c.commit()' "$1/App.mpr" "$2"
}
P="$(mkproj below)"; echo clean > "$P/.stub-state"; setver "$P" 10.24.18.103793
OUT="$("$DOCTOR" --property-sets "$P" 2>&1)"; RC=$?
[ "$RC" -eq 0 ] && printf '%s' "$OUT" | grep -q "WARN property sets:.*NOT PROOF.*Mendix 10.24.18.103793.*mxcli#1595" \
&& ok "10.24.18 clean: exit 0 but a WARN saying it proves nothing" || bad "10.24.18 clean: rc=$RC" "$OUT"
printf '%s' "$OUT" | grep -q "every unit has the property names Studio Pro writes" && bad "10.24.18 still prints the false green" "$OUT" || ok "10.24.18: no 'every unit has the property names' line"
printf '%s' "$OUT" | grep -q "mx diff <base>.mpr <mine>.mpr" && ok "names mx diff as the proof" || bad "no mx diff pointer" "$OUT"
sed -n 1p "$P/.claude/.property-sets-receipt" | grep -q " unproven " && ok "receipt records unproven, not clean" || bad "receipt" "$(cat "$P/.claude/.property-sets-receipt" 2>&1)"
n1="$(grep -c -- '--dry-run' "$P/.stub-log")"
OUT="$("$DOCTOR" --property-sets --if-stale "$P" 2>&1)"; n2="$(grep -c -- '--dry-run' "$P/.stub-log")"
[ "$n1" -eq "$n2" ] && printf '%s' "$OUT" | grep -q "WARN property sets: already checked.*NOT PROOF" && ! printf '%s' "$OUT" | grep -q "ok already checked" \
&& ok "--if-stale: not re-run, and still a WARN, never an ok" || bad "--if-stale unproven ($n1 -> $n2)" "$OUT"
OUT="$("$DOCTOR" --quick "$P" 2>&1)"
printf '%s' "$OUT" | grep -q "WARN property sets: last check for this mxcli was clean, but the model is below Mendix 10.24.24" && ok "--quick: WARN, not the ok line" || bad "--quick unproven" "$(printf '%s' "$OUT" | grep -i -A2 'property')"
for v in 10.24.23 9.24.2; do
P="$(mkproj below-$v)"; echo clean > "$P/.stub-state"; setver "$P" "$v"
OUT="$("$DOCTOR" --property-sets "$P" 2>&1)"
printf '%s' "$OUT" | grep -q "NOT PROOF" && ok "$v: below the floor, warned" || bad "$v not warned" "$OUT"
done
for v in 10.24.24 10.25.1 11.12.1; do
P="$(mkproj at-$v)"; echo clean > "$P/.stub-state"; setver "$P" "$v"
OUT="$("$DOCTOR" --property-sets "$P" 2>&1)"
printf '%s' "$OUT" | grep -q "ok property sets: Checked 3 unit(s).*every unit has the property names" && ! printf '%s' "$OUT" | grep -q "NOT PROOF" \
&& ok "$v: at or above the floor, still ok" || bad "$v wrongly warned" "$OUT"
done

echo "== P3 units need completing =="
P="$(mkproj needs)"
OUT="$("$DOCTOR" --property-sets "$P" 2>&1)"; RC=$?
Expand Down
Loading