Skip to content
Merged
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, sync-project, speed-setup): **doctor says when the speed fixes never reached a project**: `bin/exec.sh`, `bin/_common.sh` or `bin/lint-gate.sh` without the 2026-10-29 speed code (marker per file) gives a Machine health warning "speed fixes not active", and the Recommended fix leads with the exact `sync-project.sh --upgrade-bin ...` command (exec.sh always with its `_common.sh`; each copy backed up) before any other advice, because every other number measures the old scripts; `sync-project.sh` names the speed fixes on a hand-edited exec.sh / _common.sh that has the exit-code fix but not the speed work; `skills/speed-setup.md` has Claude show each local edit in one line and ask before replacing. Field: an English colleague's ExpenseTracker analysis after the toolkit update still showed a full mxbuild on every change (45.5 min, 57% of all exec time) with 15 drifted scripts in bin/; reproduced here with the real pre-#587 exec.sh / _common.sh / lint-gate.sh — doctor names all three, sync names exec.sh and refreshes the two unedited ones — Maurits (mauritscgvisser)
- fix(doctor, speed-setup): **doctor no longer says "None needed" next to a verdict of "5x slower"**: when process starts are fast but one bench step is 5x+ the reference and 2 s+ slower, the Recommended fix names that step (slow git status: `git config core.untrackedCache true` / `core.fsmonitor true`, no admin rights); a project inside a OneDrive / Dropbox / Google Drive / iCloud folder gets a Machine health warning and "move the project to a local folder" as the fix; low-memory advice now offers "restart the computer" before `--cleanup`; the bench's git step runs one untimed warm-up so a stale index after a copy or clone no longer reads as slow (field, ab-581/gk: 18.3 s cold, 31 ms warm); `skills/speed-setup.md` tells Claude never to say deno, mxbuild or helper process to a user. Field run: ab-581/gk on Linux, bench 14.7 s vs 10.9 s, "None needed" correctly; a project under a OneDrive path, move advice shown — Maurits (mauritscgvisser)
- fix(gate-check, gate-cache): **the closeout verdict cache also keys on file CONTENT, so an overwrite that keeps an old mtime (`cp -p`, `rsync -t`, `tar x`, `unzip`, `touch -d`) can no longer replay a stale verdict**: a signed triage.md copied over a FAIL one still printed the cached Stage 0 FAIL (PR #589 review); the cache now stores one `find | sort | xargs cksum | cksum` signature of the project files (skipping .git, node_modules, .mxtk, mprcontents, deployment, *.mpr, files over 1 MB) and any difference, or a signature that cannot be computed, re-evaluates; new `tests/wave2/test-gate-cache.sh` (hit, edit/delete/waiver miss, cp -p miss, TTL 0, 'not evaluated' line); `MXTK_GATE_CACHE_TRACE=1` prints `gate-cache: hit|miss` on --closeout. Field run: ab-581/base copy (197 MB, 633 hashed files) - signature 28 ms, cache hit 1464 -> 1498 ms vs 2567-2786 ms full evaluation - PR #589 reviewer, OlafFede (#583) — OlafFede (#583)
- perf(gate-check, closeout): **a Stage close starts about 70% fewer processes (Stage 3 on a small project: `--closeout` 820 -> 127 starts, `gate-check <dir> 3` 560 -> 275, no-stage run 626 -> 338; verdict lines unchanged)**: (1) `--closeout` replays the evaluation of the stage query that just ran (variables snapshotted in `<project>/.mxtk/gate-cache`, valid only while nothing under the project or the toolkit is newer than that run and the key matches; any doubt re-evaluates; `MXTK_GATE_CACHE_TTL_MIN=0` turns it off); (2) a stage query no longer evaluates obligation/artifact rows owed from later stages and says how many it skipped (the summary line is labelled the same way); (3) the register is parsed once per run instead of one awk per lookup; (4) the toolkit `git fetch` runs at most once per 15 min (`MXTK_FETCH_TTL_MIN`, `MXTK_NO_FETCH=1` unchanged); plus pure-bash trimming in the close-out block and the obligation reverse check. Reported by OlafFede (#583). — OlafFede (#583)
Expand Down
26 changes: 26 additions & 0 deletions bin/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,8 @@ fi

# recommended_fix — ONE block that ends both the normal run and --bench: the first option that works on THIS
# machine, in this order. Read-only, cheap probes; never installs anything and never asks for IT's help.
# 0. MH_STALE (machine_health): the project runs older local copies of the speed scripts -> upgrade them
# first; every other measurement is of the old path. Then MH_SYNC (project in a sync folder) -> move it.
# a. PROJECT.md says `Gate mode: full` -> remove that line: `mx check` per exec is the default, with the
# full build before every push (offered even when nothing is slow: it helps every machine).
# b. process starts are slow AND this is Git Bash AND `wsl.exe --status` succeeds -> run the toolkit in WSL.
Expand All @@ -438,6 +440,17 @@ recommended_fix() {
head_ "Recommended fix"
_rf_slow=0
[ -n "$RF_SPAWN_MS" ] && [ "$RF_SPAWN_MS" -gt "${MXTK_SPAWN_WARN_MS:-30}" ] && _rf_slow=1
if [ -n "${MH_STALE:-}" ]; then
# --only-bin scopes the install to the named scripts: --upgrade-bin alone runs the FULL sync (#390).
_rf_list="$MH_STALE"
case " $MH_STALE " in *" exec.sh "*) case " $MH_STALE " in *" _common.sh "*) ;; *) _rf_list="$_rf_list _common.sh" ;; esac ;; esac
_rf_up=""; for _rf_s in $_rf_list; do _rf_up="$_rf_up --only-bin $_rf_s --upgrade-bin $_rf_s"; done
echo " Take the toolkit's current scripts into this project; the toolkit update did not reach them,"
echo " because sync never overwrites a script that was edited here. Each copy is backed up first:"
echo " <toolkit>/bin/sync-project.sh <project>$_rf_up"
echo " Check with whoever edited them that the local change is no longer needed. Then run doctor --bench again."
return 0
fi
_rf_pm=""
if [ -n "$PROJECT_DIR" ] && [ -f "$PROJECT_DIR/PROJECT.md" ]; then
_rf_pm="$(sed -n 's/^[*_ ]*Gate mode:[*_ ]*\([A-Za-z]*\).*/\1/p' "$PROJECT_DIR/PROJECT.md" 2>/dev/null | head -1 | tr 'A-Z' 'a-z')"
Expand Down Expand Up @@ -629,6 +642,19 @@ machine_health() {
*/"Mobile Documents"/*|*/"iCloud Drive"/*) MH_SYNC=iCloud ;;
esac
[ -n "$MH_SYNC" ] && advise "the project is in a folder synced by $MH_SYNC: $_mh_pd"
# Speed fixes not active: the project's bin/ holds an OLDER local copy of a script the 2026-10-29 speed
# work changed. sync-project.sh never overwrites a hand-edited copy, so after a toolkit pull these keep
# running the slow path (field, 2026-10-30: 15 drifted scripts, every change still ran a full mxbuild).
# Marker per file = a function or file name the fixed version has; absent file = not this check's business.
MH_STALE=""
if [ -n "$_mh_pd" ]; then
for _mh_s in "exec.sh:gate_kind_decide" "_common.sh:mxtk_mx_check_to_mxbuild" "lint-gate.sh:lint-receipt-ok"; do
_mh_f="$_mh_pd/bin/${_mh_s%%:*}"
[ -f "$_mh_f" ] && ! grep -q "${_mh_s#*:}" "$_mh_f" 2>/dev/null && MH_STALE="$MH_STALE ${_mh_s%%:*}"
done
MH_STALE="${MH_STALE# }"
fi
[ -n "$MH_STALE" ] && advise "speed fixes not active in this project: older local copy of bin/$(printf '%s' "$MH_STALE" | sed 's/ /, bin\//g')"
MH_BAD="$_mh_bad"
if [ "$_mh_bad" = 1 ]; then
note "When memory runs out the operating system kills mxbuild (exit 137), and the build gate then reads"
Expand Down
8 changes: 8 additions & 0 deletions bin/sync-project.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,10 @@ known_fix_note() {
page-scope.sh)
echo "bin/page-scope.sh predates the HEADER_WORDS fix (F-042, 2026-08-28): SHOW PAGES header columns Excluded/Folder/Params were parsed as page rows, inflating the page denominator (measured: 6 real pages counted as 13), so every consumer of page-scope.json graded against furniture. One-line fix — recommended upgrade." ;;
_common.sh)
if grep -q '_mxtk_has_model' "$PROJECT_DIR/bin/_common.sh" 2>/dev/null \
&& ! grep -q 'mxtk_mx_check_to_mxbuild' "$PROJECT_DIR/bin/_common.sh" 2>/dev/null; then
echo "bin/_common.sh predates the SPEED fixes (toolkit, 2026-10-29, Windows field reports): it has no quick 'mx check' gate, so every model change runs a full mxbuild (80-130 s per change on a Windows laptop). Upgrade BOTH: --upgrade-bin _common.sh --upgrade-bin exec.sh."; return
fi
if ! grep -q '_mxtk_has_model' "$PROJECT_DIR/bin/_common.sh" 2>/dev/null; then
echo "bin/_common.sh predates the MULTI-APP ROOT fix (#420, toolkit, 2026-10-20): in a multi-app folder (apps/<X>/X.mpr) its root search climbed to the first ancestor holding any .mpr, so ledgers, stamps and lint landed in another folder. A hand-edited copy also holds back every older script that depends on it. Take just this file (your copy is backed up): $0 $PROJECT_DIR --only-bin _common.sh --upgrade-bin _common.sh"
fi
Expand All @@ -1157,6 +1161,10 @@ known_fix_note() {
fi
echo "bin/_common.sh predates the WINDOWS MXBUILD GATE fix (toolkit, 2026-08-25). This is where find_sp_app/find_mxbuild, JAVA_HOME resolution and mxtk_platform actually live — exec.sh only calls them. So upgrading exec.sh ALONE does not deliver the fix, and grepping exec.sh for mxtk_platform reports 0 even on a fully patched project: grep _common.sh instead. Without this file the mxbuild gate is skipped on every Windows exec and nothing checks your builds. Upgrade BOTH: --upgrade-bin _common.sh --upgrade-bin exec.sh." ;;
exec.sh)
if grep -q 'fail_rc' "$PROJECT_DIR/bin/exec.sh" 2>/dev/null \
&& ! grep -q 'gate_kind_decide' "$PROJECT_DIR/bin/exec.sh" 2>/dev/null; then
echo "bin/exec.sh predates the SPEED fixes (toolkit, 2026-10-29, Windows field reports): every model change runs a full mxbuild instead of the quick 'mx check' (the full build now runs before push and at module close), and each exec starts about 60% more processes, which costs most on Windows. Upgrade BOTH: --upgrade-bin _common.sh --upgrade-bin exec.sh."; return
fi
if grep -q 'PATCH_MODE' "$PROJECT_DIR/bin/exec.sh" 2>/dev/null \
&& ! grep -q 'fail_rc' "$PROJECT_DIR/bin/exec.sh" 2>/dev/null; then
echo "bin/exec.sh predates the EXIT CODES fix (#443/#448, toolkit, 2026-10-20, field project): a clean build with a lint rise exited 1 like a failed gate, and an UNVERIFIED gate exited 0 like a pass, so agents learned to ignore 1 and trust 0. Now 4 = applied, lint only; 5 = applied, unverified; 1 = not applied. Its _common.sh also retries a bundle-only mxbuild exit 3 once. Upgrade BOTH: --upgrade-bin _common.sh --upgrade-bin exec.sh."; return
Expand Down
1 change: 1 addition & 0 deletions skills/speed-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The user complains about speed, asks to update the toolkit and check whether it
1. Run `bin/doctor.sh --bench .` from the project root. It changes no project file (only git's own stat cache, as any git status does) and takes about a minute. Read the table (step, this machine, reference, factor), the "Machine health" lines and the closing "Recommended fix" block.
2. VALIDATE first: is this machine slower than it should be? Use the verdict line and the Machine health lines. If the verdict says about as fast as the reference, Machine health is clean and the block says "None needed", say so in ONE line ("This machine is already fast: <verdict>") and STOP. Do not propose a fix for a fast machine. Otherwise say the result in ONE plain sentence, from the verdict line, for example: "This machine is 6x slower than the reference, mainly because every process start takes 100 ms instead of 2 ms." If Machine health warned, say that first, in words a non-developer knows. Users do not know what deno, mxbuild or a helper process is; never use those words to them. Wrong: "47 leftover deno helpers, run --cleanup?" Right: "Mendix build processes that were left running hold 3 GB of memory. Restarting your computer clears them; or I can stop just those now, if you say yes (`bin/doctor.sh --cleanup`)." For a project in a OneDrive/Dropbox/iCloud folder, the fix is moving the project to a local folder (the Recommended fix says how).
3. Only when step 2 found a problem: carry out the "Recommended fix" with the user. It names ONE option, the first that works here:
- **Take the toolkit's current scripts** ("speed fixes not active": an older, locally edited bin/exec.sh, _common.sh or lint-gate.sh). Show the user each script's local change in one line (`diff <toolkit>/project-bin/<name> bin/<name>`), ask whether it is still needed, then run the `--upgrade-bin` command the block prints (it backs each copy up). This comes first: until then every other number measures the old scripts.
- **Remove `Gate mode: full` from PROJECT.md** (the quick `mx check` per write is the default). Show the line, ask, remove it. Nothing else changes.
- **Move the project out of a synced folder** (OneDrive, Dropbox, Google Drive, iCloud). Ask, then move or re-clone it to the local folder the block names, and reopen it there. This comes before WSL, a dev container or the cloud: a synced folder slows every option, so move first and bench again.
- **Turn on git's file cache** (git status slow, process starts fine): the `git config` lines the block prints, in the project, after asking.
Expand Down
7 changes: 7 additions & 0 deletions tests/wave2/test-bug12-sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ printf '#!/usr/bin/env bash\n# local edit; PATCH_MODE\nexit 0\n' > "$P/bin/exec.
OUT="$("$SYNC" "$P" 2>&1)"
printf '%s' "$OUT" | grep -q 'predates the EXIT CODES fix' && ok "pre-fix exec.sh: the exit-code fix is named" || bad "exit-code fix not named" "$(printf '%s' "$OUT" | grep -A2 exec.sh | head -6)"

echo "== T1c: a hand-edited exec.sh from before the speed fixes (2026-10-29) is named =="
P="$(mkproj t1c)"
printf '#!/usr/bin/env bash\n# local edit; PATCH_MODE fail_rc\nexit 0\n' > "$P/bin/exec.sh"
OUT="$("$SYNC" "$P" 2>&1)"
printf '%s' "$OUT" | grep -q 'predates the SPEED fixes' && ok "pre-speed exec.sh: the speed fixes are named" || bad "speed fixes not named" "$(printf '%s' "$OUT" | grep -A2 exec.sh | head -6)"
printf '%s' "$OUT" | grep -q 'predates the EXIT CODES fix' && bad "exit-code note fired on a copy that has fail_rc" || ok "exit-code note silent when fail_rc is present"

echo "== T2: a lost exec bit on an otherwise identical script is repaired =="
P="$(mkproj t2)"
chmod -x "$P/bin/snapshot-mpr.sh"
Expand Down
13 changes: 13 additions & 0 deletions tests/wave2/test-doctor-bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,19 @@ chkno "sync folder: repo named dropbox-client is not flagged" "$WORK/rf.out" "fo
( PATH="$SAFEPATH" MXTK_PROC_MEMINFO="$WORK/meminfo-high" FAKE_PS_FILE="$WORK/ps-clean.txt" bash "$DOCTOR" --quick "$WORK/dev/dropbox-client" > "$WORK/q-nod.out" 2>&1 ); _rc_nod=$?
grep -q 'folder synced by OneDrive' "$WORK/q-od.out" && [ "$_rc_od" = "$_rc_nod" ] \
&& ok "sync folder: WARN shown, exit code unchanged ($_rc_od)" || bad "sync folder: rc $_rc_nod -> $_rc_od or no WARN"
# 8d. Older local copies of the speed scripts -> "speed fixes not active", and upgrading them is the first fix
# (field, 2026-10-30: a project with 15 drifted scripts still ran a full mxbuild per change after the pull).
SP="$WORK/stale"; mkdir -p "$SP/bin"; printf '# P\n\nGate mode: full\n' > "$SP/PROJECT.md"
sed 's/gate_kind_decide/old_gate/g' "$ROOT/project-bin/exec.sh" > "$SP/bin/exec.sh"
cp "$ROOT/project-bin/lint-gate.sh" "$SP/bin/lint-gate.sh"
rf bash "$DOCTOR" --bench "$SP"
chk "stale scripts: health names bin/exec.sh" "$WORK/rf.out" "speed fixes not active in this project: older local copy of bin/exec.sh"
chkno "stale scripts: a current lint-gate.sh is not named" "$WORK/rf.out" "bin/lint-gate.sh"
chk "stale scripts: fix upgrades exec.sh with its _common.sh, before Gate mode" "$WORK/rf.out" "\-\-only-bin exec.sh \-\-upgrade-bin exec.sh \-\-only-bin _common.sh \-\-upgrade-bin _common.sh"
chkno "stale scripts: Gate-mode advice waits until the scripts are current" "$WORK/rf.out" 'Remove "Gate mode: full"'
cp "$ROOT/project-bin/exec.sh" "$SP/bin/exec.sh"
rf bash "$DOCTOR" --bench "$SP"
chkno "current scripts: no stale warning" "$WORK/rf.out" "speed fixes not active"
# 9. Low-memory advice offers a restart, the plain option, besides --cleanup
rf env MXTK_PROC_MEMINFO="$WORK/meminfo-low" bash "$DOCTOR" --bench "$P"
chk "low memory: restart offered" "$WORK/rf.out" "restart the computer"
Expand Down
Loading