Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
79faf2d
fix: count workflow activities with one walk in list workflows, show …
ako Oct 3, 2026
493bb7c
feat(lint): expose total_activity_count on the Starlark microflow str…
ako Oct 3, 2026
ac4e096
fix(check): report a read of a void action call's output name (MDL093…
ako Oct 3, 2026
d7bbb31
feat(catalog): add a commit ref kind for commit actions and committin…
ako Oct 3, 2026
da494c2
fix(describe): duplicate output variable warning is flow-wide
ako Oct 3, 2026
0c6baf7
fix(lsp): resolve void action calls through the workspace project
ako Oct 3, 2026
25073af
feat(catalog): record widget parent, depth, appearance and primary ac…
ako Oct 3, 2026
aafc723
feat(lint): expose widget tree, appearance and action to Starlark wid…
ako Oct 3, 2026
335e8ad
docs(skill): document the widget struct's new fields and pin action_type
ako Oct 3, 2026
63f98d1
Merge remote-tracking branch 'origin/feat/catalog-widget-tree-class-a…
ako Oct 3, 2026
00649d9
fix(catalog): the widget-tree change takes schema version 20
ako Oct 3, 2026
cb9ab80
fix(tui,eval): run mx check on a temporary copy of the project
ako Oct 3, 2026
3b03f01
fix(docker): build from a temporary copy; write only the output direc…
ako Oct 3, 2026
62256cb
docs: changelog, finding and package-operations pattern for #961
ako Oct 3, 2026
10a1403
Merge remote-tracking branch 'origin/fix/961-no-project-writes' into …
ako Oct 3, 2026
2c5a08c
fix(lint,check): MPR010 skips native pages and snippets
ako Oct 3, 2026
bff250c
test(check): merge the persistent -p flag in the MDL093 end-to-end test
ako Oct 3, 2026
fb0ddde
Merge remote-tracking branch 'origin/fix/962-check-describe-lsp' into…
ako Oct 3, 2026
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
2 changes: 2 additions & 0 deletions .claude/skills/fix-issue/findings/cmd-mxcli.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,5 @@
{"area": "cmd/mxcli", "date": "2026-10-03", "symptom": "A project whose CLAUDE.md, skills and lint rules were written by a newer mxcli is served by an older binary (v0.24.0 on PATH) with no warning: 'mdl 1;' is a parse error and shipped lint rules crash, all reading as project defects", "cause": "Nothing recorded which mxcli wrote the tooling; .claude/bootstrap-mxcli.sh linked whatever mxcli was on PATH; init --sync-skills refreshed only .ai-context/skills, never .claude/lint-rules or CLAUDE.md/AGENTS.md", "fix": "init and every sync write .ai-context/mxcli-tooling.json; root PersistentPreRun warns once on stderr when the binary is provably older (release by number, nightly by tag date, mixed by build date, dev never); sync refuses from an older binary; the bootstrap script carries a POSIX-sh copy of the ordering and neither links an older PATH binary nor keeps an older ./mxcli, downloading via a temp file + mv; sync also refreshes bundled lint rules by name and the CLAUDE.md/AGENTS.md section between mxcli:begin/end markers", "insight": "A binary cannot warn about a stamp it predates, so the guard for already-shipped binaries must live in the generated script, which the newer mxcli regenerates. The sh and Go comparisons share one test table so they cannot drift. curl -o ./mxcli on a symlinked ./mxcli would overwrite the PATH binary — always download to a temp name and rename", "issue": "ako/mxcli#952", "file": "cmd/mxcli/tooling_stamp.go; cmd/mxcli/init_tooling_sync.go; cmd/mxcli/init_hook.go (bootstrapScriptTemplate); cmd/mxcli/main.go; cmd/mxcli/init.go", "test": "cmd/mxcli/tooling_stamp_test.go; cmd/mxcli/init_hook_version_test.go; cmd/mxcli/init_tooling_sync_test.go"}
{"area": "cmd/mxcli", "date": "2026-10-03", "symptom": "CONV006 emits one finding per entity x role x CREATE/DELETE (111 on a mid-sized app), the same advice repeated per role, and the per-finding Security score is driven by role count rather than by entities", "cause": "The Starlark rule appended a violation inside the permissions_for() loop", "file": "`.claude/lint-rules/conv006_no_create_delete_rights.star` (synced to `cmd/mxcli/lint-rules/`)", "insight": "Group per entity and per right with de-duplicated sorted roles (a role can hold several access rules on one entity). Test both rule copies (.claude and the embedded one) like SEC008's test does", "refs": ["ako/mxcli#953"]}
{"area": "cmd/mxcli", "date": "2026-10-03", "symptom": "`mxcli report` could not score a project's own modules: `lint` has --modules, `report` had only --exclude", "cause": "Feature gap; and the LintContext module filter alone would not make the score exact, because project-level findings (CONV008 role mappings, project security) carry no module and are reported regardless", "file": "`cmd/mxcli/cmd_report.go`, `mdl/linter/report.go` (`ScopeToModules`, Report.Modules)", "insight": "Filter the scored violations to those located in a selected module, and print the selection in every format so a module score is not mistaken for the project's", "refs": ["ako/mxcli#953"]}
{"area": "cmd/mxcli/docker", "date": "2026-10-03", "symptom": "`mxcli docker build` (and docker run / reload, which call it) rewrote an MPRv1 project's .mpr, rewrote every MPRv2 .mxunit (restored with new mtimes), and wrote theme-cache/, deployment/, javasource/ proxies, the .launch file, .classpath and .project into the project; the TUI checker and `mxcli eval`'s mx_check wrote theme-cache/web/ and deployment/sass/ on every run", "cause": "update-widgets ran on the project under a snapshot that restored only v2 storage, and mx check / MxBuild ran on the project itself; the TUI and eval runner ran a bare `mx check <project>`", "file": "`cmd/mxcli/docker/build.go` (`buildOnCopy`), `cmd/mxcli/docker/check.go` (`MxCheckOnCopy`), `cmd/mxcli/tui/checker.go` (`runMxCheck`), `cmd/mxcli/evalrunner/checks.go` (`checkMxCheck`)", "insight": "Run update-widgets, mx check and MxBuild on one temporary copy (copyProjectToTemp) and write only the output directory. Measured on the 11.14 testapp: the PAD from the copy differs from the in-place build in the same 9 files as two in-place builds of identical copies differ (cache-bust stamps, operation ids, the native metro paths), so the output is equivalent; rebuild time unchanged (58s vs 59s), because a PAD build gains nothing from the project's deployment/. 11.14's blank app needs JDK 25, so the real-mx build test skips without it; run it with 11.13's mx.", "refs": ["ako/mxcli#961", "ako/mxcli#951", "mendixlabs/mxcli#808"]}
{"area": "cmd/mxcli", "date": "2026-10-03", "symptom": "ako/mxcli#962 item 3: in VS Code, two calls to a stored void JavaScript action with the same output name (Studio Pro's `$ReturnValueName`/`$RefreshEntity` shape) are squiggled MDL063, while `mxcli check -p` passes them", "cause": "runSemanticValidation called executor.ValidateMicroflow/ValidateNanoflow, which pass a nil void-action resolver: without the project every call output counts as a declaration", "file": "`cmd/mxcli/lsp_diagnostics.go` (runSemanticValidation); `mdl/executor/validate_void_code_calls.go` (FlowRules, CodeActionCache)", "fix": "executor.NewFlowRules(prog, s.findMprPath(), s.codeActions): resolves actions through the script and the workspace project (opened lazily), treats an unresolvable action as possibly void (unknownIsVoid) for MDL063 but never for MDL093, and shares project answers across keystrokes for 30s since one action read costs ~300ms on PedApp", "insight": "An entry point that wraps a richer internal API with nil arguments (ValidateMicroflow = validateMicroflowWith(stmt, nil)) silently gives every caller the weakest behaviour; a fix landed in the richer API (#958) does not reach them. Grep the exported wrapper's callers when the internal one gains a parameter. Measure the cost before putting project I/O on a keystroke path", "test": "`cmd/mxcli/lsp_void_calls_test.go` (PedApp: void pair clean with and without project, Boolean pair control, MDL093 only with project); `mdl/executor/validate_void_code_calls_test.go` (TestCodeActionCache_SharesProjectAnswersAcrossRuns)"}
3 changes: 3 additions & 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,6 @@
{"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"]}
{"area": "mdl/executor", "date": "2026-10-03", "symptom": "ako/mxcli#962 item 1: `$V = call java action M.VoidAction(...)` then `log ... + $V` (or `$V` as a JS call argument) passes `check` and `exec`, then mxbuild 11.13.0 fails CE0109 \"Undefined variable 'V'\"", "cause": "#953 taught MDL063 that a void call's output name declares nothing, but no rule read the other half: the name cannot be READ either. The resolver only answered void/not-void, so 'unknown' and 'non-void' were the same answer", "file": "`mdl/executor/validate_void_call_output.go` (checkVoidCallOutputUse, MDL093); `mdl/executor/validate_void_code_calls.go` (resolve -> voidness{void, known})", "fix": "MDL093: collect output names of calls KNOWN to be void, drop any name another statement defines flow-wide (declare, parameter, non-void producer), report each remaining name the flow reads (loopRefVars over every nested body). The resolver now returns known-ness, so 'possibly void' (the editor's policy) never produces MDL093", "insight": "A finding that says 'X declares nothing' has two consequences — no collision AND no definition; fixing the first and logging the second as follow-up left a CE gap. When a resolver's default is a policy (unknown counts as non-void), make the unknown state explicit before a second rule reads it: the CE0109 rule must use only knowledge, never the policy", "test": "`mdl/executor/validate_void_call_output_test.go`; `cmd/mxcli/check_void_calls_test.go` (TestCheck_ReadOfAStoredVoidCallOutput, PedApp stored void JS action, Boolean and unresolvable controls)"}
{"area": "mdl/executor", "date": "2026-10-03", "symptom": "ako/mxcli#962 item 2: describe of a microflow with the same output name in each if/else branch (or inside a loop and again after it) printed no duplicate-variable warning; mxbuild 11.13.0 reports CE0111 for both", "cause": "duplicateOutputVariableWarnings only warned when one assignment could REACH another (a reachability walk written for #710's performance), so exclusive branches and a loop body vs. the flow after it were treated as separate scopes; a test pinned the branch scoping", "file": "`mdl/executor/cmd_microflows_show.go` (duplicateOutputVariableWarnings)", "fix": "Count non-void output names over every object collection (loop bodies included); warn for any name created twice. Linear, so #710's cost concern disappears with the reachability walk", "insight": "The reachability model encoded a belief (exclusive paths may reuse a name) that no one had measured; MDL063 had already been aligned to flow-wide names in #958, so two renderings of the same rule disagreed. When one rule is corrected against mxbuild, grep for the other places that encode the same rule — here describe's header warning", "test": "`mdl/executor/cmd_microflows_duplicate_output_test.go` (TestFormatMicroflowActivitiesWarnsForExclusiveBranchOutputs, TestFormatMicroflowActivitiesNamesAreFlowWide)"}
2 changes: 2 additions & 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,5 @@
{"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"]}
{"area": "mdl/linter", "date": "2026-10-03", "symptom": "ako/mxcli#962 item 4: MPR010 'DataView contains input fields but is not inside a layout grid' fires on a page with layout Atlas_Core.NativePhone_Default (lint and check); the bare form builds clean there and following the advice is CE6858 'Please update Atlas UI to version 2.4 or higher to use Layout Grid on Native pages' (mxbuild 11.13.0, PedApp)", "cause": "Same web-only assumption MPR012 had (#953): the rule's premise is Bootstrap label/input columns, which only the web client renders, but it walked every page and snippet regardless of platform", "file": "`mdl/linter/rules/dataview_layout_grid.go` (skip ctx.NativePages(), snippets with raw Type 'Native', RequiredCatalogMode CatalogFull); `mdl/executor/validate_page_layout.go` (validatePageLayoutGridWith + projectNativeLayouts)", "fix": "Lint: skip native pages via LintContext.NativePages() (declares CatalogFull since LayoutRef is full-only — NativePages added to the catalog-mode guard list) and native snippets via the snippet's own Type. Check: ask the project whether the page's layout is native, only when the page has something to report", "insight": "A platform-specific rule needs its platform in the predicate, and there are two places a page's platform lives: the layout (pages) and the document's own Type (snippets). Also measure the ADVICE, not only the warning: here following it produced a new CE, which settles 'does it apply' faster than reasoning about rendering. A rule that starts reading NativePages() silently returns nothing on a fast catalog unless it declares CatalogFull — the guard test only catches it if the method is in its list", "test": "`mdl/linter/rules/dataview_layout_grid_test.go` (TestDataViewLayoutGridRule_SkipsNativePagesAndSnippets); `mdl/executor/validate_page_layout_test.go` (TestValidatePageLayoutGrid_SkipsNativeLayouts); `cmd/mxcli/check_native_layout_grid_test.go`"}
Loading
Loading