Skip to content

Sync ako/mxcli: incremental catalog refresh (5.7 s → 0.7 s), E015 string-function-vs-empty warning, Windows bootstrap under Git Bash - #1682

Merged
ako merged 13 commits into
mendixlabs:mainfrom
ako:main
Oct 30, 2026
Merged

ako merged 13 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into upstream: 6 commits from fork PRs ako#1816, ako#1817 and ako#1820.

Fixes #1680. Refs #580.

Incremental catalog refresh

Problem: every catalog refresh rebuilt every table from every document after any save. On a 4,753-unit app that was about 4.5 s of decoding and inserting, and 5.6–8 s end to end. This is the second step after the unit store.

Fix:

  • Row ownership: each catalog row is recorded under the document it came from.

  • What an incremental refresh rebuilds: it loads the cached catalog and deletes only:

    • the rows of the changed documents;
    • the rows of documents whose refs name a changed document;
    • the project-wide rows.

    It then rebuilds those.

  • Full-build fallback: anything the plan can't vouch for falls back to a full build. That covers folder, module, settings, security and navigation changes, another mxcli build or catalog level, and FORCE.

Refresh after one entity edit Before After
refresh catalog full 5.6–5.8 s 0.70 s
refresh catalog full source 7.3–7.9 s 0.71 s

Correctness:

  • Equivalence: TestCatalog_IncrementalBuildEqualsFullBuild and its fast-mode twin compare every table with a forced full build, across 19 kinds of edit. Each step runs a second time with no change.

  • Ref coverage: cross-document reads are recorded, and coverage is checked. It is 100% on the fixture and 99.4% on two real projects.

    • The trace found that inheritance splits had no refs edge to their entity, so show references missed those flows too. They now get a new split edge.
  • Performance guards: three tests fail if a rule of the incremental build is broken:

    • every row must belong to a document;
    • the decode budget;
    • no whole-project listings in the passes.

    Breaking one of these would leave the catalog correct but slow, so no correctness test would notice.

  • Docs: ADR-0014 records the decision and its downsides. /mxcli-dev:review gains a "Catalog builder passes" checklist.

Notes:

  • Row order and the internal Id columns of refs and permissions may differ from a full build.
  • An incremental refresh prints one incremental: N units rebuilt, M carried over line instead of per-table counts.
  • The Windows gain isn't measured.

E015: a String function's result compared with empty (refs #580)

The problem: if trim($Value) != empty then … passes check, exec and mx check, but is always true. These functions return '', never empty. Measured on 10.18.5 with mxcli test --local:

expression empty '' ' '
trim '' '' ''
toLowerCase / toUpperCase / substring / replaceAll '' '' text
toString(<empty Integer>) '' — —
$v + '' 'null' '' text
$v (control) empty '' text

The new warning:

  • E015 string-func-vs-empty is a warning, because the model is valid. It fires only for the measured functions.
  • It is skipped when the same and/or chain also compares the call with '', the guard idiom. Otherwise it was noise on a Studio Pro-authored fixture.

Not a warning: + with an empty operand gives the text null. That is documented as pitfall #18 instead, because whether an operand is empty can't be known at check time.

Not measured: Mendix 11.x and nanoflows.

init: the bootstrap script downloads the Windows asset under Git Bash (#1680)

The problem: the script built the release asset name from uname -s. Under Git Bash, MSYS2 and Cygwin that gives mingw64_nt-…, never windows, so the download returned 404. The hook runs with || true, so the session started without mxcli and nothing said why.

The fix:

  • MINGW*, MSYS* and CYGWIN* now map to windows, with the .exe suffix.
  • The binary is kept as ./mxcli.exe on Windows.
  • A stale Linux ./mxcli is removed first. Otherwise Git Bash resolves ./mxcli to that file.

Tests: they run the generated script with a fake uname and curl, for Git Bash (64- and 32-bit), MSYS2, Cygwin and Windows on ARM, with Linux and macOS as the controls.

Validation

make ci-local passes, except for two cmd/mxcli/docker TestSyncJavaDependencies_* tests that fail the same way on main in the authors' environment

claude and others added 13 commits October 30, 2026 13:10
…y (E015)

`if trim($Value) != empty` passed check, exec and mx check, and is always
true: trim, toLowerCase, toUpperCase, substring, replaceAll and toString
return '' for an empty input, never empty. Measured on the Mendix 10.18.5
runtime with `mxcli test --local`, the bare variable as the control.

E015 is a warning: the model is valid and builds, only the logic is wrong.
The fix it suggests, comparing with '', also covers an empty input.

Pitfalls gain the E015 case and a second measured one that check cannot
warn about: `+` renders an empty operand as the text 'null'.

Refs mendixlabs#580

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82rJKSgq9KUoUNdLnrL
The SessionStart bootstrap script built the release asset name from
`uname -s`, which is MINGW64_NT-10.0-26200 (or MSYS_NT-/CYGWIN_NT-) on
Windows, so it requested mxcli-mingw64_nt-10.0-26200-amd64 and got a
404. The hook runs under `|| true`, so the session then started without
mxcli.

Detect the platform once at the top of the script: map MINGW*/MSYS*/
CYGWIN* to windows with an .exe suffix, and keep the binary path in
$MXCLI (./mxcli, or ./mxcli.exe on Windows) for every guard, link,
download and invocation. On Windows, a non-executable ./mxcli (the
Linux build `mxcli new` leaves) is removed before installing
./mxcli.exe, since Git Bash would otherwise resolve ./mxcli to it.

Fixes mendixlabs#1680

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tr6TGuPpuRmehciwH
`trim($v) != empty and trim($v) != ''` has a dead first half, but the
'' comparison decides it and the condition does what it says. E015
reported it, which failed TestExprCheckRoundTrip_DescribeProducesZeroHints
on the Studio Pro-authored fixture (FeedbackModule.VAL_Feedback): noise on
real projects, not the silent wrong result the rule exists for.

The check now runs over the finished tree and is skipped when the same
and/or chain compares the same call with ''. A guard on another call or
in another chain still reports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82rJKSgq9KUoUNdLnrL
exprcheck: warn when a String function's result is compared with empty (E015)
Every refresh rebuilt every catalog table from every unit after any save:
about 4.5 s of decoding and inserting on a 4,753-unit project, 5.6-8 s end
to end. Each row is now recorded under the unit it came from (a wrapper on
the build's transaction; passes mark their owner with own()), and the next
build loads the cached catalog, deletes the rows of the changed units, the
units whose refs name them and the project-wide rows, and rebuilds only
those, listing and decoding only those through a unit-scoped backend view.
Anything the plan cannot vouch for falls back to a full build. A refresh
after one edit on that project now takes 0.7 s, with or without the source
index.

Tests compare every table with a forced full build over 19 kinds of edit
in full and fast mode; guards count unowned rows, units decoded per
incremental refresh and whole-project listings. ADR-0014 records the
decision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82ttaGzay5CwiC2V7cf
…es (#1804)

An incremental build rebuilds a document when a unit its refs name changes,
so refs must cover every other document a pass reads for it. The lookups
that read other documents (the member-ref index, association ends, entity
and flow names by ID) now report each name they read per document, and
DependencyCoverage checks every read the way the plan would.
TestCatalog_RefsCoverEveryCrossDocumentRead holds the fixture at 100%;
TestCatalog_RefCoverageOfProject measures a real project on demand (99.4%
on FactoryManagement and EVORA - ICX).

The trace found that a flow branching on a specialization (`split type $x
when M.E`) had no edge to the entity, which `show references` missed too:
it gets a `split` edge now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82ttaGzay5CwiC2V7cf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82ttaGzay5CwiC2V7cf
fix(init): bootstrap script downloads the Windows asset under Git Bash
Rebuild only the changed units on a catalog refresh (#1804)
@ako
ako merged commit 763288f into mendixlabs:main Oct 30, 2026
15 checks passed
sergiusz-woznicki pushed a commit to sergiusz-woznicki/mxcli that referenced this pull request Nov 5, 2026
…tion-24oro5

fix: six describe → exec losses from the JTSBootLogboek round trip (mendixlabs#1676–mendixlabs#1681)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants