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 .claude/skills/fix-issue/findings/mdl-executor.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -850,3 +850,4 @@
{"area": "mdl/executor", "date": "2026-10-03", "symptom": "ako/mxcli#950 item 1: a page button described as `Action: delete close page` executes to a Forms$DeleteClientAction with ClosePage=false — a describe → exec round trip silently stops the button closing its page; check, exec and mx check are all clean", "cause": "buildClientActionV3Base's `delete` case did not copy action.ClosePage, although the visitor sets it and the writer (clientActionToGen) writes it; save/cancel copied it", "file": "`mdl/executor/cmd_pages_builder_v3.go` (buildClientActionV3Base, case \"delete\")", "insight": "The audit of the other cases found no other dropped visitor flag, but two hard-coded ones describe cannot express: complete task always writes ClosePage/Commit true and show page/create object write NumberOfPagesToClose2 \"\" — every Studio Pro instance in TestApp/PedApp has those values, so they are latent, not live. A text round trip could not have caught this: describe printed the flag correctly and the second describe of the exec'd page printed `delete`, which looks like a user edit — read the stored ClosePage", "refs": ["ako/mxcli#950"]}
{"area": "mdl/executor", "date": "2026-10-03", "symptom": "ako/mxcli#950 item 2: describe of a flow ending `return [%CurrentUser%];` prints `return $[%CurrentUser%];`, which does not parse; `return if … then … else …` likewise became `return $if …` (two TestApp WorkflowCommons microflows)", "cause": "formatActivity's EndEvent branch added `$` to any return value without one of + ' \" ( ) — a character blacklist standing in for 'is a bare variable name'", "file": "`mdl/executor/cmd_microflows_format_action.go` (isBareReturnVariable)", "insight": "Restore a stripped sigil only for the positive shape it was stripped from (a bare name, optionally /path); a blacklist of characters lets every new expression form through. The other `$`-adding sites in describe prefix variable-NAME fields, not expressions, and are safe", "refs": ["ako/mxcli#950"]}
{"area": "mdl/executor", "date": "2026-10-03", "symptom": "ako/mxcli#950 item 3: describe of a navigation list prints item actions as `show_page 'Mod.Page'` (does not parse — TestApp Rules.Entity_Menu, 3 syntax errors); with that fixed, exec refuses the description with `item inside navigationlist requires a name` because Studio Pro leaves items unnamed", "cause": "extractNavigationListItemAction had a private copy of the page-action rendering in the legacy form instead of the shared renderClientActionMDL; buildNavigationListItemV3 required a name Studio Pro never stores; and once exec accepted it, the writer wrote `Name: \"\"` and no ConditionalVisibilitySettings where Studio Pro stores no Name key and a null slot (6 of 6 items in TestApp), so GetPut still rewrote the snippet", "file": "`mdl/executor/cmd_pages_describe_parse.go` (extractNavigationListItemAction), `mdl/executor/cmd_pages_builder_v3_widgets.go` (buildNavigationListItemV3), `mdl/executor/cmd_pages_describe_output.go` (item header), `mdl/backend/modelsdk/widget_write.go` (navListItemToGen, Forms$NavigationListItem NullFields)", "insight": "Fixing the reported parse error only exposed the next law: the issue said the empty item name 'parses fine', which was true and irrelevant — exec refused it, and after that the writer rewrote it. An unnamed item with no Name key passes mx check at 11.14.0, contrary to the old ledger note that the key is mandatory (that applies to a NAMED item's key, not its absence). Run the whole describe → check → exec → describe chain on the Studio Pro-authored document before declaring a round-trip bug fixed", "refs": ["ako/mxcli#950"]}
{"area": "mdl/executor", "date": "2026-10-03", "symptom": "`list workflows` and `show structure` report fewer workflow activities than the catalog's workflows_data (TestApp Workflow1: 5 vs 8)", "cause": "cmd_workflows.go countFlowActivities and cmd_structure.go countStructureFlowActivities each recursed over outcome flows only, skipping boundary-event flows and event sub-processes; the catalog had moved to a shared walk in #937 and the executor copies were left behind", "file": "`mdl/backend/wfnames/walk.go` (`WalkActivities`, `CountActivities`), `mdl/executor/cmd_workflows.go`, `mdl/executor/cmd_structure.go`, `mdl/catalog/workflow_walk.go`", "insight": "Duplicate-resolver drift: fixing one copy of a traversal (#937) left two private copies answering differently. The walk now lives in wfnames, which both catalog and executor already import, so there is one place to add a new sub-flow slot. Grep for every recursion over `workflows.Flow` when one is fixed.", "refs": ["ako/mxcli#963", "ako/mxcli#937"]}
1 change: 1 addition & 0 deletions .claude/skills/fix-issue/findings/mdl-other.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,4 @@
{"area": "mdl/linter", "date": "2026-10-03", "symptom": "mxcli report scores a project lower for lint rules that crash: under v0.24.0, project rules written by a newer mxcli (QUAL004, CUSTOM002 reading .document_noun_title) each produced an error-severity 'Starlark rule error: \"microflow\" struct has no .document_noun_title attribute' that counted 10 points against the project", "cause": "StarlarkRule.Check turned every evaluation error into an ordinary SeverityError violation, indistinguishable from a finding; BuildReport and Summarize counted it, and a configured rule severity was applied to it too", "fix": "ruleFailureViolation marks every failure Violation.RuleFailure; a missing struct attribute (matched on the evaluator message, since starlark flattens NoSuchAttrError via fmt.Errorf) becomes info 'rule <ID> needs a newer mxcli (<detail>)'; BuildReport splits RuleFailures out before counting and every report format lists them separately; Linter.Run skips the severity override for them; an 'undefined:' load failure gets a newer-mxcli hint", "insight": "The score measures the project, so anything about the tooling has to be partitioned out BEFORE counting, not filtered in the formatter. The control that makes the score assertion meaningful is a working rule's finding that does move the score", "issue": "ako/mxcli#952", "file": "mdl/linter/starlark.go (ruleFailureViolation); mdl/linter/report.go (BuildReport); mdl/linter/linter.go (Run); mdl/linter/report_format.go", "test": "mdl/linter/starlark_rule_failure_test.go"}
{"area": "mdl/linter", "date": "2026-10-03", "symptom": "MPR012 (legacy static/dynamic image, CE0582) fires on pages with a native layout (Atlas_Core.NativePhone_Default), where mxbuild 11.13 builds them clean; `check --references` likewise refuses a classic `dropdown` on a native page as MDL-WIDGET40 (CE0582), also clean in mxbuild", "cause": "Both rules assume every page is rendered by the React client. Nothing recorded a layout's platform: ListLayouts left pages.Layout.Native false for every layout, and catalog layouts had only LayoutType, which cannot tell the platforms apart (native uses Default/Popup)", "file": "`mdl/backend/modelsdk/page.go` (`layoutIsNative`), `mdl/catalog/builder_pages.go` (layouts.Platform), `mdl/linter/context_catalog_tables.go` (`NativePages`), `mdl/linter/rules/legacy_image_widget.go`, `mdl/executor/validate_widget_attribute_type.go` (`layoutIsNative`)", "insight": "The platform is the content wrapper's TYPE (Forms$NativeLayoutContent), not a property. The native layouts live in Atlas_Core, a Marketplace module, so the page->layout join must not apply the notPlatformModule filter the iterators use. Sibling check MDL-WIDGET39 (CE2421, textbox on an enumeration) is NOT React-only: measured CE2421 on the native page too, so it keeps firing there", "refs": ["ako/mxcli#953"]}
{"area": "mdl/linter", "date": "2026-10-03", "symptom": "MPR002 'Microflow X has no activities' on a microflow or nanoflow whose only content is `return <expr>;` (e.g. a label formatter, `return $currentUser;`)", "cause": "ActivityCount excludes start and end events, so a flow that computes its result in the end event's return value counts 0 activities", "file": "`mdl/linter/rules/empty.go` (`returnsValue`)", "insight": "A non-Void ReturnType is the catalog's witness that the end event returns a value (mxbuild requires it on every end event), so no new column was needed; '' and 'Void' stay reported", "refs": ["ako/mxcli#953"]}
{"area": "mdl/catalog", "date": "2026-10-03", "symptom": "`commit $Order` (on a loop iterator, a parameter or a retrieved list) wrote no refs row; refs_to(entity) could not answer which flows commit an entity", "cause": "refs had no commit ref kind: microflowActionRef / microflowVarActionRef emitted create/change/delete only, and a create/change with commit carried no commit edge", "file": "`mdl/catalog/builder_references.go` (`microflowCommitRef`, `RefKindCommit`)", "insight": "Commit is a use of the entity type like change/delete, resolved through the same intra-flow varEntity map (so the loop-iterator fix of #1266 applies for free), and emitted as a second edge beside create/change rather than replacing them. It stays out of graphRefKinds (would double existing flow->entity edges) and callerRefKinds (a type use, not an invocation). The ref_kind vocabulary test now reads every RefKind constant from the declarations, so a new kind cannot ship undocumented.", "refs": ["ako/mxcli#963", "mendixlabs/mxcli#1266", "mendixlabs/mxcli#1267"]}
5 changes: 3 additions & 2 deletions .claude/skills/mendix/write-lint-rules/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ def check():
| `description` | string | Documentation text |
| `return_type` | string | Return type |
| `parameter_count` | int | Number of parameters |
| `activity_count` | int | Number of activities |
| `activity_count` | int | Number of activities at the top level of the flow, excluding start/end events and merges. A loop counts as one; its body is not counted |
| `total_activity_count` | int | `activity_count` plus every activity inside a loop, at any depth — the size of the flow including loop bodies. Equal to `activity_count` for a flow without loops |
| `complexity` | int | McCabe cyclomatic complexity |
| `document_noun` | string | `"microflow"`, `"nanoflow"` or `"rule"` — for mid-sentence use in a message |
| `document_noun_title` | string | `"Microflow"`, `"Nanoflow"` or `"Rule"` — for `document_type=` and a message that opens with it |
Expand Down Expand Up @@ -551,7 +552,7 @@ Returned by `permissions()` (all types) or `permissions_for()` (entity-specific)
| `target_type` | string | What it points AT, upper-case: `"ENTITY"`, `"ASSOCIATION"`, `"MICROFLOW"`, `"NANOFLOW"`, `"RULE"`, `"PAGE"`, `"LAYOUT"`, `"WORKFLOW"`, `"WIDGET"`, `"JAVA_ACTION"`, `"REST_OPERATION"`, `"REGULAR_EXPRESSION"`, `"ATTRIBUTE"`, `"ENUMERATION"`, `"ENUMERATION_VALUE"`. `LAYOUT`, `WIDGET`, `ATTRIBUTE`, `ENUMERATION` and `ENUMERATION_VALUE` are only ever targets; `SCHEDULED_EVENT` and `PROJECT_SETTINGS` only ever sources |
| `target_id` | string | Target UUID |
| `target_name` | string | `"Sales.Customer"`; three-part for an attribute or an enumeration value: `"Sales.Order.Total"`, `"Sales.OrderStatus.Open"` |
| `ref_kind` | string | How it references: `"call"`, `"create"`, `"retrieve"`, `"change"`, `"delete"`, `"show_page"`, `"datasource"`, `"action"`, `"layout"`, `"parameter"`, `"return"`, `"generalize"`, `"associate"`, `"home_page"`, `"login_page"`, `"menu_item"`, `"calculate"`, `"schedule"`, `"validate"`, `"settings"`, `"widget"`, `"sync"`, `"publish"`, `"event"`, `"member"` (binds/reads/writes an attribute or navigates an association), `"xpath"` (an XPath constraint names it), `"type"` (typed as an enumeration), `"value"` (an expression names an enumeration value), `"mapping"` (an import/export mapping maps the entity) — lower-case, unlike the types above. Attribute names used only through a variable in a free-text expression (`$Order/Total`) have no edge |
| `ref_kind` | string | How it references: `"call"`, `"create"`, `"retrieve"`, `"change"`, `"delete"`, `"commit"` (a commit action, or a create/change that commits — beside its `"create"`/`"change"` edge; a commit of a variable whose entity the flow cannot tell has no edge), `"show_page"`, `"datasource"`, `"action"`, `"layout"`, `"parameter"`, `"return"`, `"generalize"`, `"associate"`, `"home_page"`, `"login_page"`, `"menu_item"`, `"calculate"`, `"schedule"`, `"validate"`, `"settings"`, `"widget"`, `"sync"`, `"publish"`, `"event"`, `"member"` (binds/reads/writes an attribute or navigates an association), `"xpath"` (an XPath constraint names it), `"type"` (typed as an enumeration), `"value"` (an expression names an enumeration value), `"mapping"` (an import/export mapping maps the entity) — lower-case, unlike the types above. Attribute names used only through a variable in a free-text expression (`$Order/Total`) have no edge |
| `module_name` | string | Source module |

### project_security
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Fixed

- **`list workflows` and `show structure` count every activity of a workflow** (ako/mxcli#963) — including those on a boundary-event path and in an event sub-process, as the catalog's `workflows_data` has since #937. TestApp `Workflow1` listed 5 activities where the catalog counted 8; all three now share one walk (`wfnames.WalkActivities`).
- **`docker check` no longer modifies the project** (ako/mxcli#951) — `mx update-widgets` and `mx check` now run on a temporary copy, for MPR v1 and v2 alike, and what mx prints names the project's own paths. Before, a check rewrote an MPR v1 project's `.mpr` permanently (only v2 was restored from a snapshot), and `mx check` itself rewrote `theme-cache/` and created `deployment/sass/` even with `--no-update-widgets`. The output now says that widget definitions were normalised on a copy, and that a CE0463 the stored project still has is therefore not reported: `--no-update-widgets` checks the project as stored, `mxcli fix widgets` applies the normalisation (ako/mxcli#568, #646). Build output, caches and VCS folders are not copied; the copy goes to `$TMPDIR` and is removed afterwards.
- **Parallel mxcli runs on one project no longer fail to save the catalog cache** (ako/mxcli#951) — eight parallel `lint` runs on a fresh copy printed `failed to create table catalog_meta: table catalog_meta already exists` or `database is locked`. The cache is now written to a temporary file next to it and renamed into place, so every run saves and a reader sees the old cache or the new one, never a half-written file; opening a current cache no longer writes to it.
- **A lint rule that fails no longer costs the project score** (ako/mxcli#952) — a Starlark rule reading a struct field this mxcli does not expose (a rule written for a newer mxcli, such as one using `document_noun_title` under v0.24.0) is reported at info level as `rule <ID> needs a newer mxcli (<detail>)` instead of an error. All rule failures are kept out of `mxcli report`'s score, summary and categories and listed in their own "Rules That Could Not Run" section (`ruleFailures` in JSON); other failures stay `Starlark rule error` errors in `mxcli lint`. A configured rule severity no longer applies to the rule's own failure. A rule file that fails to load on an undefined name says the rule may need a newer mxcli. Measured on PedApp with v0.24.0 and rules from main: QUAL004 and CUSTOM002 crashed and scored as 2 errors.
Expand Down Expand Up @@ -132,6 +133,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- **A `commit` reference kind in the catalog** (ako/mxcli#963) — `refs` has a `commit` edge from a flow to the entity it commits: a commit action, or a create / change that commits (`Yes` or `YesWithoutEvents`), the latter beside its `create` / `change` edge. The entity of a committed variable is resolved like `change` / `delete`, loop iterators included, so `commit $Order` inside `loop $Order in $Orders` now has a row. `refs_to("M.Order")` in a Starlark rule answers which flows commit an order. `commit` is not in the analysis graph and not a caller kind. The catalog schema version is bumped, so a cached catalog rebuilds.
- **`total_activity_count` on the Starlark microflow struct** (ako/mxcli#963) — the catalog's `TotalActivityCount` (loop bodies included, at any depth) for every flow `microflows()` yields: microflows, nanoflows and rules. `activity_count` keeps counting a loop as one activity.
- **A project records which mxcli wrote its tooling, and an older binary says so** (ako/mxcli#952) — `mxcli init` and every `init --sync-skills` write `.ai-context/mxcli-tooling.json` (version, build time, date; rewritten only when the version changes). Any command that opens the project with `-p` and a binary **older** than the stamp warns once on stderr, naming both versions and how to update; `init --sync-skills` from an older binary **refuses** instead of rolling the skills, rules and CLAUDE.md back. Releases compare by number, nightlies by tag date, a release against a nightly by build date; dev builds are never reported. Binaries from v0.24.0 and earlier cannot read the stamp, so the regenerated `.claude/bootstrap-mxcli.sh` checks it before choosing a binary: an older `mxcli` on PATH is not linked in (it downloads `MXCLI_TAG` instead), an older `./mxcli` is replaced, and the download lands through a temporary file so a `./mxcli` symlink never has it written through into the PATH binary.
- **`init --sync-skills` (alias `--sync`) refreshes the bundled lint rules and the mxcli section of CLAUDE.md / AGENTS.md** (ako/mxcli#952) — it used to refresh only the skills, so a project kept the lint rules and guidance of whichever mxcli first initialised it. Bundled rules are recognised by file name; your own rules beside them are never touched. CLAUDE.md and AGENTS.md are now written between `<!-- mxcli:begin … -->` / `<!-- mxcli:end -->` markers, and only that section is refreshed — by the sync and by a re-run of `mxcli init` — so project notes outside the markers survive. A file written before the markers is left alone by the sync, with a note; run `mxcli init` once to adopt them.
- **`mxcli init` and `mxcli new` create `mdlsource/`** (ako/mxcli#952) — the directory the generated CLAUDE.md says scripts live in, with a README.
Expand Down
9 changes: 9 additions & 0 deletions docs-site/src/internals/catalog-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ rather than trusting a list here:
|---------|------|
| `call` | flow calls a microflow / nanoflow / rule / Java action / REST operation |
| `create` / `change` / `delete` / `retrieve` | flow acts on an entity object |
| `commit` | flow commits an entity object: a commit action, or a create / change that commits (`Yes` or `YesWithoutEvents`), beside its `create` / `change` edge |
| `return` | flow returns an entity type |
| `parameter` | page or flow parameter entity type |
| `generalize` | entity extends entity |
Expand All @@ -278,6 +279,14 @@ rather than trusting a list here:
| `validate` | attribute validation rule uses a regular expression |
| `widget` | page or snippet uses a pluggable / custom widget |

`change`, `delete` and `commit` act on a *variable*, so the entity is resolved
within the flow — from a parameter, a create or retrieve output, or a loop
iterator over one of those. A variable whose entity the flow cannot tell (a
microflow call's result, for one) has no edge. `commit` is not in the analysis
graph: the variable it commits comes from a parameter, create or retrieve that
already links the flow to the entity (or to the association it was retrieved
over), so it would mostly double existing edges.

`schedule`, `publish`, `event` and `settings` are **entry points**: something
outside the call graph runs the microflow, so nothing in the model calls it.
They are what stops `GRAPH_DEAD_ASSETS`, `LIST CALLERS OF` and lint rule QUAL004
Expand Down
Loading
Loading