From 3759d1bad8ef8260de3b24646d306e29e244bece Mon Sep 17 00:00:00 2001 From: MendixMau Date: Thu, 15 Oct 2026 22:30:26 +0800 Subject: [PATCH 1/3] fix(sync-project): no baseline-skills warning for the toolkit wiring block or a missing heading (#382) Strip mxtk:wiring block before the grep; warn only when the Integration heading exists. Test T4 flipped, T7-T9 added. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 +++- bin/sync-project.sh | 20 ++++++++++++++------ tests/wave2/test-sync-dup-heading-exit.sh | 20 ++++++++++++++++++-- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e238acd..18c5490d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ updating it became a separate chore). One line per change: Kinds: `new` · `fix` · `learn` (a skill/learning) · `process` (rules, templates, CI). Credit the person or project that surfaced the change — the credit line is the thank-you. -**Release cycle (from 2026-09-19).** New lines land under `## Unreleased`, at the top. Every few +**Release cycle (from 2026-09-19).** New lines land under `## Unreleased + +- fix(sync-project): **the "Baseline-only skills cited" warning no longer fires on the toolkit's own wiring block, and no longer sizes a section that does not exist.** The check grepped all of `CLAUDE.md` for `skills/learned-mdl-preflight.md` / `skills/query-the-model.md`, and `wire-agents.sh`'s `mxtk:wiring` block cites the first, so every sync flagged the toolkit's own text; with no "mxcli-project-toolkit Integration" heading it then printed a whole-file word count as "heading to EOF". The wiring block is now stripped before the grep, and the warning is raised only when the heading exists. `tests/wave2/test-sync-dup-heading-exit.sh` T4 flips (no heading: no warning) and gains T7-T9. — joshuamoesa (#382)`, at the top. Every few days the maintainer cuts a release with `bin/cut-release.sh`: the `## Unreleased` heading becomes `## vYYYY.MM.DD`, master is tagged `vYYYY.MM.DD`, and a fresh empty `## Unreleased` opens. So the top section is always "what changed since the last release", a project reports the release it is diff --git a/bin/sync-project.sh b/bin/sync-project.sh index 1770bc32..329749de 100755 --- a/bin/sync-project.sh +++ b/bin/sync-project.sh @@ -1058,17 +1058,25 @@ if [ -f "$PROJECT_DIR/CLAUDE.local.md" ]; then # can also trip it; the message below is phrased as "check whether" for that reason, not as # a flat assertion. Same rule as the ledger-row warn above: CLAUDE.md is bootstrap-project.md's # (an LLM merge), not this script's, so sync never edits it — only names the fix. - if [ -f "$PROJECT_DIR/CLAUDE.md" ] && grep -qE 'skills/(learned-mdl-preflight|query-the-model)\.md' "$PROJECT_DIR/CLAUDE.md"; then + # The toolkit's own wiring block (mxtk:wiring:start..end, written by wire-agents.sh) cites + # skills/learned-mdl-preflight.md in its item-3 sentence, so it is stripped before the grep: + # otherwise the toolkit flags its own text as the adopter's duplicate (#382). And the check + # only speaks when the "mxcli-project-toolkit Integration" heading exists: with no heading + # there is no section to name or size, and a whole-file word count described as "heading to + # EOF" pointed the adopter at text that is not theirs to edit. + _dup_from_line="" + if [ -f "$PROJECT_DIR/CLAUDE.md" ]; then + _dup_body="$(awk '/mxtk:wiring:start/{skip=1} !skip{print} /mxtk:wiring:end/{skip=0}' "$PROJECT_DIR/CLAUDE.md")" # `|| true`: no heading is the COMMON case (every CLAUDE.md mxcli init v0.25 writes cites # the skills without it). Unguarded, grep's exit 1 fails the pipeline under pipefail and # errexit ended the whole sync here, silently, before the bin/ and tests/e2e refresh — # every sync from 9a0350b (2026-09-18) to this fix. Regression: tests/wave2/test-sync-dup-heading-exit.sh. - _dup_from_line="$(grep -n 'mxcli-project-toolkit Integration' "$PROJECT_DIR/CLAUDE.md" | head -1 | cut -d: -f1 || true)" - if [ -n "$_dup_from_line" ]; then - _dup_words="$(tail -n "+$_dup_from_line" "$PROJECT_DIR/CLAUDE.md" | wc -w | tr -d ' ')" - else - _dup_words="$(wc -w < "$PROJECT_DIR/CLAUDE.md" | tr -d ' ')" + if printf '%s\n' "$_dup_body" | grep -qE 'skills/(learned-mdl-preflight|query-the-model)\.md'; then + _dup_from_line="$(printf '%s\n' "$_dup_body" | grep -n 'mxcli-project-toolkit Integration' | head -1 | cut -d: -f1 || true)" fi + fi + if [ -n "$_dup_from_line" ]; then + _dup_words="$(printf '%s\n' "$_dup_body" | tail -n "+$_dup_from_line" | wc -w | tr -d ' ')" warn "CLAUDE.md cites Baseline-only skills also covered by CLAUDE.local.md — check whether" \ "its \"mxcli-project-toolkit Integration\" section (~$_dup_words word(s), heading to" \ "EOF, not all of it necessarily routing) is a duplicated Baseline routing block." \ diff --git a/tests/wave2/test-sync-dup-heading-exit.sh b/tests/wave2/test-sync-dup-heading-exit.sh index 504c0424..d5a68343 100755 --- a/tests/wave2/test-sync-dup-heading-exit.sh +++ b/tests/wave2/test-sync-dup-heading-exit.sh @@ -48,8 +48,8 @@ printf '%s\n' "$OUT" | grep -qE 'artifact\(s\) would be updated|All copied artif && ok "T3 no heading: sync reaches its summary line" \ || bad "T3 no heading: sync reaches its summary line" printf '%s\n' "$OUT" | grep -q 'cites Baseline-only skills' \ - && ok "T4 no heading: the duplicate-routing warning is still raised" \ - || bad "T4 no heading: the duplicate-routing warning is still raised" + && bad "T4 no heading: no duplicate-routing warning (nothing to name or size, #382)" \ + || ok "T4 no heading: no duplicate-routing warning (nothing to name or size, #382)" # --- heading present: the case the check was written for keeps working ------------------ P2="$(mkproj withheading)" @@ -60,6 +60,22 @@ printf '%s\n' "$OUT2" | grep -qE 'section \(~[0-9]+ word' \ && ok "T6 heading: warning sizes the section" \ || bad "T6 heading: warning sizes the section" +# --- #382: a citation inside the toolkit's own wiring block is not the adopter's duplicate --- +P3="$(mkproj wiringonly)" +printf '\n\nsee skills/learned-mdl-preflight.md STOP table\n\n' >> "$P3/CLAUDE.md" +OUT3="$("$SYNC" "$P3" --dry-run 2>&1)"; RC3=$? +[ "$RC3" -eq 0 ] && ok "T7 wiring block only: sync exits 0" || bad "T7 wiring block only: sync exits 0 (got $RC3)" +printf '%s\n' "$OUT3" | grep -q 'cites Baseline-only skills' \ + && bad "T8 wiring block only: no duplicate-routing warning" \ + || ok "T8 wiring block only: no duplicate-routing warning" +# the same text OUTSIDE the block, under the heading, still warns +P4="$(mkproj wiringplusheading)" +printf '\n\nsee skills/learned-mdl-preflight.md\n\n\n## mxcli-project-toolkit Integration\n\nRead skills/query-the-model.md.\n' >> "$P4/CLAUDE.md" +OUT4="$("$SYNC" "$P4" --dry-run 2>&1)" +printf '%s\n' "$OUT4" | grep -qE 'section \(~[0-9]+ word' \ + && ok "T9 block + heading: warning still raised, sized from the heading" \ + || bad "T9 block + heading: warning still raised, sized from the heading" + echo "" echo "test-sync-dup-heading-exit: $PASS passed, $FAIL failed" [ "$FAIL" -eq 0 ] From 0528c1124cd23e12340658d1e146549f0ec6a7ca Mon Sep 17 00:00:00 2001 From: MendixMau Date: Thu, 15 Oct 2026 23:02:41 +0800 Subject: [PATCH 2/3] fix(review #398): CHANGELOG line under ## Unreleased; T4/T8 use if/else `grep && bad || ok` counted a failure as both FAIL and ok (bad returns 1). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 5 ++--- tests/wave2/test-sync-dup-heading-exit.sh | 16 ++++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18c5490d..393c5b7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,7 @@ updating it became a separate chore). One line per change: Kinds: `new` · `fix` · `learn` (a skill/learning) · `process` (rules, templates, CI). Credit the person or project that surfaced the change — the credit line is the thank-you. -**Release cycle (from 2026-09-19).** New lines land under `## Unreleased - -- fix(sync-project): **the "Baseline-only skills cited" warning no longer fires on the toolkit's own wiring block, and no longer sizes a section that does not exist.** The check grepped all of `CLAUDE.md` for `skills/learned-mdl-preflight.md` / `skills/query-the-model.md`, and `wire-agents.sh`'s `mxtk:wiring` block cites the first, so every sync flagged the toolkit's own text; with no "mxcli-project-toolkit Integration" heading it then printed a whole-file word count as "heading to EOF". The wiring block is now stripped before the grep, and the warning is raised only when the heading exists. `tests/wave2/test-sync-dup-heading-exit.sh` T4 flips (no heading: no warning) and gains T7-T9. — joshuamoesa (#382)`, at the top. Every few +**Release cycle (from 2026-09-19).** New lines land under `## Unreleased`, at the top. Every few days the maintainer cuts a release with `bin/cut-release.sh`: the `## Unreleased` heading becomes `## vYYYY.MM.DD`, master is tagged `vYYYY.MM.DD`, and a fresh empty `## Unreleased` opens. So the top section is always "what changed since the last release", a project reports the release it is @@ -21,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- fix(sync-project): **the "Baseline-only skills cited" warning no longer fires on the toolkit's own wiring block, and no longer sizes a section that does not exist.** The check grepped all of `CLAUDE.md` for `skills/learned-mdl-preflight.md` / `skills/query-the-model.md`, and `wire-agents.sh`'s `mxtk:wiring` block cites the first, so every sync flagged the toolkit's own text; with no "mxcli-project-toolkit Integration" heading it then printed a whole-file word count as "heading to EOF". The wiring block is now stripped before the grep, and the warning is raised only when the heading exists. `tests/wave2/test-sync-dup-heading-exit.sh` T4 flips (no heading: no warning) and gains T7-T9. — joshuamoesa (#382) - new(mxpatch, exec): **`project-bin/mxpatch.py`, the shared patch core, and a `mxcli fix hashes` check around every `exec.sh --patch` run: the two shipped workflow patchers no longer leave a stale ContentsHash.** mxpatch finds a unit by type + name (+ module) with a byte pre-filter, finds an element by Name, gets/sets a value by key, and writes the unit and its `.mpr` ContentsHash (base64 SHA-256 of the file) in one step; dry run by default, `already applied` on a re-run, and a refusal (exit 3) when the current value is not the expected old one. `wf-set-call-captions.py` and `wf-add-path-terminators.py` now write through it, refuse drift (a caption is replaced only while it is still the stamped microflow name or the `--expect-caption` value; `--expect-paths N` for terminators), and also take the `exec.sh --patch` form (` Module.Workflow`). The index is committed before the file swap, so a kill mid-write leaves a MISMATCH that `fix hashes` reports and a re-run repairs. `mxunit_bson.py` reads BSON 0x09 (UTC datetime). `exec.sh --patch` runs `mxcli fix hashes` (v0.25.0+, probed via `fix --help`) before and after the script and restores the snapshot on any issue the baseline did not have, before mxbuild, which cannot see it. Field evidence: captured mxcli v0.25.0/v0.26.0 runs on a public model (`tests/wave2/fixtures/mxpatch/CAPTURE.txt`): master's patchers leave a MISMATCH, the ported ones verify clean; the hash routine agrees with mxcli on 67 of 67 mismatched and 302 matching units of a second model. Widget-shape copying and pluggable-widget properties wait on a captured page unit. Field run ([#390](https://github.com/MendixMau/mxcli-project-toolkit/issues/390), mxcli v0.26.0, macOS): PASS, and the patch repaired a ContentsHash MISMATCH an old-style `--patch` had left on the unit it rewrote. Fixes from it: the backup of a rewritten unit is kept outside `mprcontents/` (in `$MXPATCH_BACKUP_DIR`, which `exec.sh --patch` points into the snapshot it just took, else a 0700 `/mxpatch-bak-/`; refused when it resolves inside `mprcontents/`, through a symlink or another case spelling; named with pid and counter so two writes in one second keep both), no longer as an untracked `.mxunit.bak` that `git add mprcontents` would commit; the BUILD-LOG row carries the hash bracket (`hashes: baseline 1 mismatch (pre-existing), verify clean (1 pre-existing repaired by this patch's rewrite)`); and the baseline message says that a unit the patch rewrites gets a fresh hash, and to repair the others with `fix hashes --repair`. A baseline MISMATCH that is still reported on a unit the patch rewrote counts as new (restore, exit 1), not as pre-existing. — jopterhorst ([#294](https://github.com/MendixMau/mxcli-project-toolkit/issues/294), [#295](https://github.com/MendixMau/mxcli-project-toolkit/issues/295)) - learn(pipelines/mendix-mpr/samples): **three minimal mxcli repros from the native-module bug hunt, the files the upstream draft cites** (split from [#293](https://github.com/MendixMau/mxcli-project-toolkit/pull/293), 6/6). `fsm-template-9.24.2/native-bughunt/` holds CAPTURE.txt and D1–D3, which are written from scratch in a `Repro` module: `describe entity` printing `index ( desc);`, the native Repeater `content` slot, and no flow writable to a 9.24.2 project. They are the repros for U3, U2 and U14 in `bug-logs/pending-github-issues/2026-10-12-mxcli-0.26-round-trip-report.md`. The other 472 sample files of #293 had no citing reader, because the skills and Rehost splits state their field results inline, so none of them ships. — MendixMau, Decompose/Rehost lab runs 2026-10-12 - learn(inbox, pending-github-issues): **ten field-note drops from the Decompose/Rehost lab runs, and one not-filed draft of 53 mxcli 0.26 defects for mendixlabs/mxcli** (split from [#293](https://github.com/MendixMau/mxcli-project-toolkit/pull/293), 5/6). `contrib/inbox/2026-10-1{2,3}-*.md`: increments 1–8 of the split-a-monolith runs on a public Marketplace template (FSM 9.24.2), the native-module bug hunt, and the GenAI Showcase regeneration (0.26.0 and the 2026-10-13 nightly); each now has the template's front matter and is at most 600 words. `bug-logs/pending-github-issues/2026-10-12-mxcli-0.26-round-trip-report.md`: the U1–U53 table the inbox drops refer to, marked DRAFT — NOT FILED, to be split into one issue per defect and retested before anything is posted. No BUG numbers assigned; triage promotes. — MendixMau, Decompose/Rehost lab runs 2026-10-12/13 diff --git a/tests/wave2/test-sync-dup-heading-exit.sh b/tests/wave2/test-sync-dup-heading-exit.sh index d5a68343..e2afd663 100755 --- a/tests/wave2/test-sync-dup-heading-exit.sh +++ b/tests/wave2/test-sync-dup-heading-exit.sh @@ -47,9 +47,11 @@ printf '%s\n' "$OUT" | grep -q 'Would install: bin/exec.sh' \ printf '%s\n' "$OUT" | grep -qE 'artifact\(s\) would be updated|All copied artifacts up to date' \ && ok "T3 no heading: sync reaches its summary line" \ || bad "T3 no heading: sync reaches its summary line" -printf '%s\n' "$OUT" | grep -q 'cites Baseline-only skills' \ - && bad "T4 no heading: no duplicate-routing warning (nothing to name or size, #382)" \ - || ok "T4 no heading: no duplicate-routing warning (nothing to name or size, #382)" +if printf '%s\n' "$OUT" | grep -q 'cites Baseline-only skills'; then + bad "T4 no heading: no duplicate-routing warning (nothing to name or size, #382)" +else + ok "T4 no heading: no duplicate-routing warning (nothing to name or size, #382)" +fi # --- heading present: the case the check was written for keeps working ------------------ P2="$(mkproj withheading)" @@ -65,9 +67,11 @@ P3="$(mkproj wiringonly)" printf '\n\nsee skills/learned-mdl-preflight.md STOP table\n\n' >> "$P3/CLAUDE.md" OUT3="$("$SYNC" "$P3" --dry-run 2>&1)"; RC3=$? [ "$RC3" -eq 0 ] && ok "T7 wiring block only: sync exits 0" || bad "T7 wiring block only: sync exits 0 (got $RC3)" -printf '%s\n' "$OUT3" | grep -q 'cites Baseline-only skills' \ - && bad "T8 wiring block only: no duplicate-routing warning" \ - || ok "T8 wiring block only: no duplicate-routing warning" +if printf '%s\n' "$OUT3" | grep -q 'cites Baseline-only skills'; then + bad "T8 wiring block only: no duplicate-routing warning" +else + ok "T8 wiring block only: no duplicate-routing warning" +fi # the same text OUTSIDE the block, under the heading, still warns P4="$(mkproj wiringplusheading)" printf '\n\nsee skills/learned-mdl-preflight.md\n\n\n## mxcli-project-toolkit Integration\n\nRead skills/query-the-model.md.\n' >> "$P4/CLAUDE.md" From 79174ed40773f96d6a8e363f6df34d911eee90d6 Mon Sep 17 00:00:00 2001 From: MendixMau Date: Fri, 16 Oct 2026 01:17:36 +0800 Subject: [PATCH 3/3] fix(sync-project): here-string instead of a pipe into grep -q (review #398) tests/wave2/test-pipefail-grep-q.sh flagged the new check under pipefail. Co-Authored-By: Claude Opus 5.5 --- bin/sync-project.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/sync-project.sh b/bin/sync-project.sh index 329749de..5a8fa567 100755 --- a/bin/sync-project.sh +++ b/bin/sync-project.sh @@ -1071,7 +1071,7 @@ if [ -f "$PROJECT_DIR/CLAUDE.local.md" ]; then # the skills without it). Unguarded, grep's exit 1 fails the pipeline under pipefail and # errexit ended the whole sync here, silently, before the bin/ and tests/e2e refresh — # every sync from 9a0350b (2026-09-18) to this fix. Regression: tests/wave2/test-sync-dup-heading-exit.sh. - if printf '%s\n' "$_dup_body" | grep -qE 'skills/(learned-mdl-preflight|query-the-model)\.md'; then + if grep -qE -- 'skills/(learned-mdl-preflight|query-the-model)\.md' <<<"$_dup_body"; then _dup_from_line="$(printf '%s\n' "$_dup_body" | grep -n 'mxcli-project-toolkit Integration' | head -1 | cut -d: -f1 || true)" fi fi