Repository navigation
fix(lint): rename the toolkit's CONV020 rule — mxcli v0.28 ships a built-in CONV020 - #573
Merged
Merged
Conversation
….28 ships a built-in CONV020
mxcli v0.28.0 adds a built-in CONV020 ("Created object never persisted",
mendixlabs/mxcli#1518) and has no duplicate-ID guard, so the toolkit's
user-feedback CONV020 ran beside it under one id. Captured on a public
model (tests/wave2/fixtures/lint-conv020-v028/): two CONV020 findings,
`lint -l` lists CONV020 twice, `CONV020: enabled: false` switches both off.
- lint-rules/mxtk_ux003_action_user_feedback.star: RULE_ID MXTK-UX003.
- install-lint-rules.sh: _mxtk_lint_migrate_renames (MXTK_LINT_RENAMES in
install-manifest.sh) retires an unedited old copy (md5 of any shipped
version, CR-insensitive, or the receipt row), rekeys baseline findings
whose message is the toolkit rule's, carries a disabled.txt line over;
an edited copy is left alone with a warning and the new rule held back;
counts-only baselines, lint-config keys and PROJECT.md lines get a NOTE.
- lint-gate.sh --update-baseline records mxcli_version; doctor.sh prints
the re-baseline hint when the installed mxcli differs (no extra call).
- tests/wave2/test-lint-rule-rename.sh: 75 cases, both layouts.
Field proof: golden lint captures from the v0.28.0 test pass (pedapp
@ 37855fe3); no run against a real project with the rule installed yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…fig opt-out, NOTE once - install-lint-rules.sh: on the unedited-copy migration, a lint-config.yaml `CONV020: enabled: false` (README's preferred opt-out) is carried over as a lint-rules-disabled.txt line for mxtk_ux003_action_user_feedback.star, so the renamed rule is not installed. The CONV020 key is left alone (on v0.28 it now switches off only the built-in); the NOTE says it was carried over. Scoped read is lint-gate's SEC014 awk, narrowed to `enabled: false`. - The counts-only baseline NOTE prints once (marker line in .claude/.mxtk-lint-notes). - The baseline rekey spawns Python only when a key under the old id carries a toolkit-rule message (grep pre-filter), not on every sync once the built-in CONV020 is baselined. - doctor.sh: a baseline with no recorded mxcli_version gets no --update-baseline command. - conv010 comment change reverted (it caused a "Lint rule refreshed" diff in every project). - bug log pointer names MXTK-UX003. - test-lint-rule-rename.sh: T1b (opt-out baseline from the capture taken with the opt-out; fails 4/4 per layout on 5c8adb6: MXTK-UX003 installed, lint-gate 1 -> 2 findings), T5 second-run assertion, T7 no-command assertion. T1 now uses a non-opt-out CONV020 setting. 95/95 here, 83/95 on 5c8adb6. Field run: pedapp (public, mendixlabs/mxcli testdata) + conv020-setup.mdl, mxcli v0.28.0, scaffolded by master init-project.sh, CONV020 enabled: false, baselined, synced: this branch 14 -> 14 findings, "PASS — nothing got worse" (rc 1 only from PERF002/UI001 blindness); 5c8adb6: MXTK-UX003 0 -> 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
…G: keep both sides) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
… does (second review) - _mxtk_lint_cfg_disabled: false is plain false/False/FALSE or n/N/no/No/NO/off/Off/OFF (plain or quoted), per mxcli's RuleConfigYAML.Enabled *bool decoded by gopkg.in/yaml.v3 v3.0.1 (resolve.go resolveMapList + decode.go's YAML 1.1 fallback for typed bools). Rule key bare, single- or double-quoted; `enabled` only as a direct child or a top-level flow-map key, so `options: enabled: false` no longer carries over an opt-out mxcli ignores. - Config read from the first of .claude/lint-config.yaml, lint-config.yaml, .lint-config.yaml (mxcli FindConfigFile order). - Hold path: the warning gives the lint-rules-disabled.txt line that keeps the opt-out. - Dry run no longer says it would install a rule it says it would carry an opt-out over for. - NOTE drops the move-the-key advice when the new rule is already off. - .mxtk-lint-notes: comment that it is committed on purpose (team-shared). - Fixture: T1c (8 spellings/locations, verdicts from a new mxcli v0.28.0 capture lint-config-variants-v0.28.0.out), T1d dry run, T3b hold path, T4 NOTE. 137/137 here; 28 failures at 0944408. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
Round-3 review of #573: isoff() kept the trailing \r, so a lint-config.yaml saved with CRLF (Git Bash, core.autocrlf) lost the CONV020 opt-out; mxcli v0.28 reads it as off. Strip \r first (the change-log.sh idiom). T1c gains a crlf row: fails on ded779c (opt-out lost, lint-gate rc 1, both layouts), 141/0 now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Track: FIX (maintainer decision #12).
Direct lane
What changed and why (one paragraph):
mxcli v0.28.0 (2026-10-23) adds a built-in lint rule
CONV020("Created object never persisted", mendixlabs/mxcli#1518). The toolkit's own page-action user-feedback rule was alsoCONV020. mxcli has no duplicate-ID guard (mdl/linter/linter.go,AddRuleappends without checking), so both rules run under one ID. Collision evidence, captured verbatim on the public pedapp model (mendixlabs/mxcli testdata @ 37855fe3), committed undertests/wave2/fixtures/lint-conv020-v028/with provenance inCAPTURE.txt:Microflow 'Shop.SUB_Seed' creates $Orphan …(built-in) and'ACT_SaveItem' is triggered from a page …(toolkit).-r CONV020runs both rules.CONV020: enabled: falseswitches both off.lint -llists CONV020 twice (CreatedObjectNeverPersisted, ActionUserFeedback).project-bin/lint-gate.shadds the findings of both rules into one count.The rule is now
MXTK-UX003(lint-rules/mxtk_ux003_action_user_feedback.star). The rename coversRULE_ID, the three_ruleself-reports, the header and every reference.Why that ID. The toolkit's existing rule IDs either share mxcli's own families or sit beside them, and that convention can collide. CONV020 itself and SEC014 are in mxcli's own numbered series. PERF001 appears as a test fixture ID in mxcli. The
MXTK-prefix is one mxcli does not use, and the v0.28.0-lcapture contains noMXTK-ID.UX003continues the toolkit's UX family (UX001 and UX002). Hyphenated IDs already parse in exec.sh and lint-gate.sh, as MDL-FLOW01 shows. The stock replacements (CONV009, CONV010, ARCH002, ARCH003) keep their stock IDs on purpose, because they replace the filemxcli initseeds. This is documented inlint-rules/README.mdunder "Rule IDs".Migration (
_mxtk_lint_migrate_renamesinbin/lib/install-lint-rules.sh, driven byMXTK_LINT_RENAMESininstall-manifest.sh). It runs on everysync-project.shandinit-project.shthroughmxtk_install_lint_rules, and is idempotent:PROJECT_MODULES = (), sync notes that the new rule will now report findings.lint-baseline.json: findings whose message matches the toolkit rule's message pattern move to MXTK-UX003, and the counts move with them. Findings from the built-in keepCONV020. The regex is checked against the captured messages. A baseline that holds counts only (written before [bug] lint-gate: ratchet prints 3 arbitrary examples and baselines counts, so a rise cannot be attributed (agent re-baselined on a wrong attribution) #239) cannot be split, so sync prints a NOTE once (a marker line in.claude/.mxtk-lint-notesstops repeats) and does not guess. Python runs only when a key under CONV020 carries a toolkit-rule message (grep pre-filter), so a baselined built-in CONV020 costs nothing on later syncs.lint-rules-disabled.txt: a line naming the old file name is rewritten to the new name. A file name identifies the rule without ambiguity.lint-config opt-out carried over: when an unedited copy is migrated and the lint config switches
CONV020off (README's preferred opt-out), sync writesmxtk_ux003_action_user_feedback.star # reason: carried over from lint-config.yaml CONV020: enabled: false (<date>)tolint-rules-disabled.txt, so the renamed rule is not installed. The config read is the first of.claude/lint-config.yaml,lint-config.yaml,.lint-config.yamlat the project root — mxcli'sFindConfigFileorder. On a single-tree project that is the file mxcli reads; on a two-tree project (.mprunderapp/) mxcli reads the config beside the.mprinstead, so the carry-over follows the root file the toolkit itself uses (same as lint-gate and sec014-notice; tracked as a separate toolkit-wide issue). The CONV020 key is left alone: on v0.28 it now switches off only the built-in, which the same key already had off. Accepted spellings are exactly what mxcli v0.28.0 treats as false (RuleConfigYAML.Enabled *booldecoded by gopkg.in/yaml.v3 v3.0.1):enabled:false,False,FALSE, orn,N,no,No,NO,off,Off,OFF(plain or quoted), under a bare,'single'- or"double"-quotedCONV020key, as its direct child or in a flow map on the key's line (CONV020: { enabled: False, severity: info }), in LF or CRLF files. Not an opt-out, as in mxcli:enablednested underoptions:,fAlse/nO/0, and a quoted"false"(mxcli fails to load that config and runs every rule).lint-config.yamlCONV020 keys andPROJECT.mdlines that name CONV020: one NOTE in the run that migrates (saying so when the opt-out was carried over), and no rewrite. Their text cannot say which rule was meant, and PROJECT.md decision rows have no structured waiver format.After an mxcli upgrade (must-do 2).
lint-gate.sh --update-baselinenow recordsmxcli_versionin the baseline. That costs one--versioncall, on update runs only, never on a check run.bin/doctor.shcompares it with the--versionline it already reads for its receipt fingerprint, so there is no extra mxcli call. When they differ, doctor prints the hintbin/lint-gate.sh --update-baseline --reason "mxcli vX built-in rules". When the baseline records no version, doctor notes it and says the next--update-baselinewill record it, with no command: there is no evidence of a rise yet. It is a note only, so the exit code does not change. The same hint is documented inskills/lint-that-actually-runs.md, which is an on-demand skill and not a baseline file. The gate's blocking behaviour is unchanged.--strict, a project with an edited legacyconv020copy now gets a counted warning on every sync until it migrates by hand. This is a deliberate exit-code change for that case only (pending maintainer decision #19).exec.sh(one comment),lint-gate.sh,install-lint-rules.sh,install-manifest.shanddoctor.sh. The baseline rekey needs Python 3 throughresolve_py; without it sync warns and changes nothing. New artifact read:mxcli_versioninlint-baseline.json, written bylint-gate.sh --update-baseline.bin/render-routing.sh --checkbefore and after). No routed or always-loaded file grew.--versioncall on--update-baselineonly. doctor: no extra calls.Fixtures. The new fixture
tests/wave2/test-lint-rule-rename.sh bin/sync-project.shhas 141 cases and passes. Projects are scaffolded by the realinit-project.shand migrated by the realsync-project.sh. Baselines are written by the reallint-gate.shfrom the golden captures. Every case runs on both layouts (single-tree and.mprunderapp/). The cases cover:lint-config.yamlCONV020: enabled: falseopt-out is carried over: new rule not installed, lint-gate rc 0 (T1b)Scoped fixtures run and green (at 5c8adb6; later re-runs are under the Review fixes sections):
test-lint-delivery.shtest-lint-receipt-revert,test-lint-gate-acceptandtest-lint-gate-new-findings. All three were updated for the new name. The firsttest-lint-gate-acceptrun caught a regression: my--versioncall ran after lint, which clobbered an arg-recording stub. Fixed by reading the version before lint.test-exec-lint-autorestore,test-exec-gate-retryandtest-exec-lock-watchtest-stock-hashes(with v0.28.0) andtest-manifest-clean-checkouttest-sec014-noticeandtest-bug12-synctest-common-root-multiapptest-doctor-gate-selftest,test-doctor-docker-probe,test-doctor-mxcli-archandtest-doctor-stale-hookstest-property-setsandtest-progress-report-gitignorebin/check-portability.shandbin/check-scripts.share clean.Review fixes (independent review of 5c8adb6, fixed in 9c3b891):
rules: CONV020: enabled: falsegot MXTK-UX003 switched back on and lint-gate failedMXTK-UX003 0 -> 1. Now carried over (migration item 4). New fixture case T1b: baseline is the verbatim capture taken with the opt-out (lint-conv020-disabled-v0.28.0.out, no toolkit finding); it fails 4 assertions per layout on 5c8adb6 (rule installed, no disabled line, no carried-over NOTE, lint-gate rc 1) and passes now. T1 now uses a non-opt-out CONV020 setting so it still exercises the rename path.--update-baselinecommand for an unversioned baseline; T7 asserts it.bug-logs/mxcli-bugs.mdCONV020 pointer says "(now MXTK-UX003)".conv020-setup.mdl, mxcli v0.28.0, scaffolded by master'sinit-project.sh,CONV020: enabled: false, baselined, synced with this branch: 14 -> 14 findings, "PASS — nothing got worse" (rc 1 only from the unrelated PERF002/UI001 blindness). The same steps with 5c8adb6:MXTK-UX003 0 -> 1.Review fixes, round 2 (second review of 0944408, fixed in ded779c):
_mxtk_lint_cfg_disabledsaw only a lowercasefalseunder a bare or double-quoted key, soFalse,FALSE,no, a flow map and a'CONV020':key lost the opt-out (lint-gate0 -> 1), and anenabled: falseunderoptions:carried one over that mxcli ignores. Now the yaml.v3 false set, any key quoting, direct child or flow map only; the NOTE grep takes the same key forms.mdl/linter/config.goFindConfigFile).lint-rules-disabled.txtline that keeps an opt-out (a listed rule is not installed; a copy left inlint-rules/would still run, because mxcli loads every*.starthere, so the step replaces copying the rule, not follows it).lint-rules-disabled.txtline)..claude/.mxtk-lint-notes: comment that it is committed on purpose, team-shared like.mxtk-lint-receipt(it marks a NOTE about the committed baseline as said).lint-config-variants-v0.28.0.out: mxcli v0.28.0 on the pedapp capture model, once per spelling/location (28 runs); fixture T1c takes its verdicts from it. New cases T1c (8 variants), T1d (dry run), T3b (hold path), T4 NOTE: test-lint-rule-rename 137/137 at ded779c, 109/137 (28 failures) at 0944408.Review fixes, round 3 (third review of ded779c, fixed in cb50a09):
\r, so a config saved with CRLF (Git Bash,core.autocrlf) lost the opt-out, although mxcli v0.28 reads it as off (the round-1 reader matched it). The awk now strips\rfirst, thebin/lib/change-log.shidiom. T1c gains a CRLF row: on ded779c it fails on both layouts (opt-out lost, lint-gate rc 1); test-lint-rule-rename 141/141 now; check-portability clean.resolve.go,decode.go) and real v0.28 runs; precedence matches FindConfigFile; mxcli loads every*.starinlint-rules/(mdl/linter/starlark.go), so the hold-path wording stands; the awk constructs are BSD-awk safe by inspection.rules:, YAML anchors/aliases/!!booltags, multi-line flow maps, and configs mxcli fails to load for another reason. Two-tree config location: separate toolkit-wide issue.Field evidence / field-proof gap. The golden input is real v0.27.0 and v0.28.0 output on a public model. No real client project with the toolkit rule installed has been migrated or linted with this branch (the opt-out path was field-run on the public pedapp model, see Review fixes). The maintainer should run this before merge, on a real project on mxcli v0.28.0 that has the old rule installed:
mxcli lint -p <app>.mpr --format jsonbefore the sync, to see two kinds of CONV020 finding.bin/sync-project.sh <project>.mxcli lint --format jsonagain: toolkit findings now report asMXTK-UX003, and only the built-in reports as CONV020.bin/lint-gate.sh: the only rise should be the built-in CONV020.bin/doctor.sh <project>for the re-baseline hint.macOS and Git Bash were not run. Checked by inspection:
sed -i.b,native_pathfor the Python path, BSD-awk constructs; CRLF configs are covered by a fixture case.CHANGELOG.mdline appendedOpen questions for the maintainer:
MXTK-IDs too, before mxcli takes those numbers? (BUG-127..133 and two skill learnings from a sales-coaching build (post-merge commits of PR #3's branch) #18)--strictwarning for an edited legacy copy what you want, or should it be a NOTE? (status.sh, doctor --quick, exec.sh preflight, bug-lookup.sh, walking-skeleton — toolkit CLI improvements from the greenfield pilot #19; unchanged by the review fixes, pending your decision.)tests/wave2/test-sec014-notice.shstill usesCONV020as an example "other rule" key. Its meaning is unchanged, so I left it.Review: pending re-review of cb50a09
🤖 Generated with Claude Code
https://claude.ai/code/session_013jQvaonym9V1wKKfAEPmRr