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(lint): **the toolkit's CONV020 (page actions give user feedback) is now `MXTK-UX003` (`mxtk_ux003_action_user_feedback.star`), because mxcli v0.28.0 ships a built-in CONV020 ("Created object never persisted", mendixlabs/mxcli#1518) and has no duplicate-ID guard.** Captured on a public model (`tests/wave2/fixtures/lint-conv020-v028/`): v0.28.0 reported two CONV020 findings, one per rule, `-l` listed CONV020 twice, and `CONV020: enabled: false` switched both off. `bin/sync-project.sh` migrates projects: an unedited old copy (md5 of any shipped version, CR-insensitive, or the receipt row) is replaced, `lint-baseline.json` findings whose message is the toolkit rule's are rekeyed to MXTK-UX003 (the built-in's stay CONV020), and a `lint-rules-disabled.txt` line follows the file. A lint-config opt-out of CONV020 is carried over too: a `lint-rules-disabled.txt` line for the new file keeps MXTK-UX003 off (the key stays; on v0.28.0 it switches off only the built-in). It is read the way mxcli v0.28.0 reads it (yaml.v3, captured per spelling): `enabled:` `false`/`False`/`FALSE` or `n`/`no`/`off` in any of yaml.v3's casings (`No`, `NO`, `Off`, `OFF`…, plain or quoted), as a direct child of a bare, single- or double-quoted `CONV020` key or in its flow map, from the first of `.claude/lint-config.yaml`, `lint-config.yaml`, `.lint-config.yaml`; an `enabled` nested under `options:` and a quoted `"false"` (mxcli fails to load that config) are not opt-outs. An edited copy is left in place with a warning and manual steps (with the `lint-rules-disabled.txt` line to keep such an opt-out), and the new rule is not installed beside it. A counts-only baseline, `lint-config.yaml` keys and `PROJECT.md` lines that name CONV020 get a NOTE, not a guess, printed once. New toolkit rules take an `MXTK-` id (`lint-rules/README.md` "Rule IDs"). After any mxcli upgrade, `lint-gate.sh --update-baseline` now records `mxcli_version`, and `bin/doctor.sh` prints the re-baseline hint when the installed mxcli differs from the recorded one (a baseline with no recorded version gets a note, no command; `skills/lint-that-actually-runs.md`). Decision #12 — mxcli v0.28.0 test pass
- process(routing, mdl-agent): **the mdl helper no longer preloads the kind-specific MDL skills at Stage 5; it loads them per script through `bin/mdl-rules.sh`** — lazy-load PR 2b. `learned-microflow-patterns.md`, `microflow-preflight.md` and `ui-preflight-pages.md` move to on-demand; `learned-mdl-preflight.md`, `design-spacing.md`, `learned-detection-gaps.md`, `security-is-not-a-later-script.md` and `retesting-learned-rules.md` become on-demand for mdl only (`mdl?`), unchanged for every other role; a new baseline `mdl-rules` row (Stages 5 and 6, so a Stage 6 mdl pack keeps the detection-gaps and retesting rules; mdl Stage 6 preload 9,476 words) replaces them. Baseline 72,891 → 63,877 words; mdl Stage 5 preload 29,388 → 14,600 (per script, after reading the `mdl-rules` output, the helper reads ≈21.4k words for page only, ≈22.5k for microflow+access, ≈28.1k for page+microflow, ≈29.5k for all kinds: ≈0–8k saved by kind; the A/B covered rule sufficiency with kinds pre-selected, not step 4a) (the 3,347-word core of `bin/mdl-rules.sh` counted, like the runbook slice). The mdl stub's workflow step 4a and report line ask for "STOP rows for kinds K: n/n checked". `context-pack.sh`'s "Rules" list names `<toolkit>/bin/mdl-rules.sh <kinds>` with the step's kinds (page / microflow / entity) — after this change the only route `microflow-preflight`, `security-is-not-a-later-script`, `design-spacing`, `learned-detection-gaps` and `retesting-learned-rules` have to a pack-fed helper (fixture: a page step and a logic step). `gate-check.sh` build-ready item 2 accepts `bin/mdl-rules.sh` as well as `ui-preflight-pages`, so a project synced after this change does not flip to FAIL (fixture: both layouts). No skill text changed — lazy-load plan, option C (maintainer)
- new(mdl-rules): **`bin/mdl-rules.sh <kind...> | --for <script.mdl>` prints the MDL rules one script needs, by the kinds of statement it writes** — lazy-load PR 2a. `bin/lib/mdl-rule-kinds.tsv` maps page / microflow / access / workflow / entity / any to sections located by file and heading text (never line numbers): the STOP rows by their `Kind` column, the kind-specific gotchas, `learned-microflow-patterns.md`, `microflow-preflight.md`, `ui-preflight-pages.md`, `design-spacing.md` §1–3, the detection-gaps register rows by construct, `security-is-not-a-later-script.md` and `retesting-learned-rules.md`. Output is the core (Step 0, the `any` STOP rows, gotchas, final self-check: 3,347 words) plus the kind sections, closed by "Rules for kinds K: STOP rows n/n listed" (page 10,223 words, microflow 10,186, access 4,730, entity 5,183, workflow 3,732), against the 29,388 words the mdl helper preloads at Stage 5 today. `--for` reuses exec.sh's statement classifier (`exec.sh --subjects`; Java actions and mappings count as microflow, demo users as access) and exits 1 if it fails; a heading that is gone fails loudly, never prints empty. `skills/generated/rules-<kind>.md` are the same output for harnesses that read files (Copilot, Cursor, Windsurf); `--check` (CI) fails on drift, on any STOP row, gotcha, register row or `##` section that reaches no kind (a section left out on purpose needs an `exclude` row with its reason), and on any register ERE that matches no row by itself, and on an unclosed code fence in a file it reads. No routing change yet: PR 2b demotes the preloads. Behaviour suite: `build-packs.sh --rules "<kinds>"` expands the script in a pack. Fixture `tests/wave2/test-mdl-rules.sh` — lazy-load plan, option C (maintainer)
- process(preflight): **`learned-mdl-preflight.md` moves its STOP-row narrative out of the baseline and gains a `Kind` column (page / microflow / access / workflow / any)** — lazy-load PR 1. The 15 retired rows collapse to one line listing their numbers, the version notes move, and each live row keeps its If/Then text plus one failure clause; the full rows, retired rows and version notes are in `process/baseline-incident-history.md` § PF1, verbatim (`check-moved-text.sh`: 43 of 43 removed lines found). The workflow `DECISION` STOP and the #879 read-first pointer stay in the skill. `Kind` sits at the end of each row so `mdl-scan`'s `| <n> 🔎` markers still match. Baseline 73,946 → 72,891 words (−1,055). — maintainer (Option C lazy-loading plan)
Expand Down
27 changes: 26 additions & 1 deletion bin/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,32 @@ fi

# --- verdict ---------------------------------------------------------------------------------

# Line 2 of the receipt below: the installed mxcli's --version line. Read here, once (it was read
# inside the receipt block before), so the lint-baseline hint below needs no extra mxcli call.
FP_MXCLI="none"
[ -n "$PROJECT_DIR" ] && [ -x "$PROJECT_DIR/mxcli" ] && FP_MXCLI="$("$PROJECT_DIR/mxcli" --version 2>/dev/null | head -1)"

# Lint baseline vs this mxcli. A new mxcli can add built-in rules (v0.28.0: CONV020) that raise
# lint on the next exec with no model change. lint-gate.sh --update-baseline records
# mxcli_version. Notes only: the exit code does not change, the gate's behaviour does not either.
_lb="${PROJECT_DIR:-.}/.claude/lint-baseline.json"
_lb_now="$(printf '%s\n' "$FP_MXCLI" | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
if [ -n "$PROJECT_DIR" ] && [ -f "$_lb" ] && [ -n "$_lb_now" ]; then
_lb_was="$(sed -n 's/.*"mxcli_version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$_lb" | head -1)"
if [ "$_lb_was" != "$_lb_now" ]; then
head_ "Lint baseline: $PROJECT_DIR"
# No recorded version is no evidence of a rise: name no command (a reflex re-baseline is what
# skills/lint-that-actually-runs.md warns against). A recorded, different one is evidence.
if [ -z "$_lb_was" ]; then
note "lint baseline records no mxcli version; if mxcli was upgraded since it was taken, new built-in rules can raise lint."
note " the next bin/lint-gate.sh --update-baseline records it; nothing to do until lint actually rises."
else
note "lint baseline was taken with mxcli $_lb_was; this is $_lb_now. New built-in rules can raise lint."
note " after reviewing the new findings: bin/lint-gate.sh --update-baseline --reason \"mxcli $_lb_now built-in rules\""
fi
fi
fi

head_ "Verdict"

# Leave a dated receipt in the project so gate-check can tell "doctor ran and passed" from
Expand All @@ -1558,7 +1584,6 @@ if [ -n "$PROJECT_DIR" ] && [ -d "$PROJECT_DIR" ]; then
mkdir -p "$PROJECT_DIR/.claude" 2>/dev/null || true
# Line 2 is the environment fingerprint: mxcli version, mxbuild path, arch. exec.sh re-runs
# `doctor.sh --quick` when it changes or the receipt is over a day old (warn-only).
FP_MXCLI="$( [ -x "$PROJECT_DIR/mxcli" ] && "$PROJECT_DIR/mxcli" --version 2>/dev/null | head -1 || echo none)"
{ printf '%s %s fail=%s warn=%s%s\n' "$(date '+%Y-%m-%d %H:%M')" "$RECEIPT_VERDICT" "$FAIL" "$WARN" "$( [ "$QUICK" = 1 ] && echo ' quick')"
printf 'fingerprint: %s | %s | %s\n' "$FP_MXCLI" "${MXBUILD:-no-mxbuild}" "$(uname -sm)"
[ -n "$GATE_SELFTEST_LINE" ] && printf 'gate-selftest: %s\n' "$GATE_SELFTEST_LINE"
Expand Down
2 changes: 1 addition & 1 deletion bin/init-project.sh
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ fi
# ── Starlark lint rules ──────────────────────────────────────────────────────────────────
# mxcli seeds .claude/lint-rules/ with its own copies; three of them are broken as shipped
# (two match no entity at all and report a clean pass forever). Install the toolkit's fixed
# versions over the stock ones, and conv020 — which mxcli does not ship — alongside them.
# versions over the stock ones, and mxtk_ux003 (MXTK-UX003) — which mxcli does not ship — alongside them.
# Nothing a human edited is overwritten: see bin/lib/install-lint-rules.sh.
#
# THIS MUST RUN AFTER wire-agents.sh. That script runs `mxcli init`, and `mxcli init` re-seeds
Expand Down
Loading
Loading