diff --git a/CHANGELOG.md b/CHANGELOG.md index 53a3e5a4..c5acd1d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- fix(lock): **reap tickets close the stale-takeover race; one shared lock in `_common.sh`.** The old takeover serialised reaping through one `.takeover` file that went stale by pid or age and was moved aside and put back with `ln`; a contender creating a new one in that gap put two contenders inside the takeover, and one's compare-then-rm deleted the other's fresh lock. Now a dead holder's line X is reaped under tickets `.reap..` (written aside, then `ln`ed: exclusive, never empty); ticket N+1 is allowed only when ticket N's pid is dead, never by age; only the highest ticket's holder may `rm` X, and only while the lock still holds exactly X; the new holder clears other keys' tickets. Lock lines gain a `nonce=` field, so a line never comes back. `mxtk_lock_take` / `mxtk_lock_release` / `mxtk_lock_live` replace exec.sh's `exec_lock_*` and test-scratch.sh's `take_over`/`holder_live` copies; both scripts stop with the sync remedy on an older `_common.sh`. The lock path, line format and liveness rule stay readable by the old code, and a live old-style `.takeover` is waited on, so mixed versions do not deadlock. New `tests/wave2/bench-lock-race.sh` (SIGKILLs at test anchors and at random 0–50 ms; `MXTK_LOCK_TEST_PAUSE` hook, no-op unset) and `test-lock-reap.sh`; `test-shared-lock-takeover.sh` and `test-test-scratch.sh` source `_common.sh` instead of awk-lifting the code. An empty stale lock is removed only while it is still over a minute old (an empty line is not unique), and a give-up caused by a live reaper names its pid and ticket file and says to resume or end it (review of #379). Field evidence: none yet; needs one macOS and one Git Bash harness run. — r-eng-backlog item 1, MendixMau - new(sync-project): **`bin/sync-project.sh --only-bin [--only-bin …]` installs only the named `project-bin/` scripts into the project's `bin/` and touches nothing else; `--upgrade-bin` is now documented as what it is — a full sync that may also overwrite a modified copy.** A field run used `--upgrade-bin exec.sh --upgrade-bin _common.sh --upgrade-bin test-scratch.sh` as "install just these scripts" and got a full sync: 244 tracked files changed, one deleted, ~30 added, git hooks reinstalled. `--only-bin` decides everything before writing anything (a name outside the install manifest, NOINSTALL scripts included, a path-like or empty name, a locally modified copy without `--upgrade-bin `/`--force`, or an older copy the full sync's `_common.sh` refresh hold would keep, exits 1 with the project untouched; names are never glob-expanded), backs up an overwritten copy, and prints every file it wrote. The remedy lines in `app-restart.sh`, `test-scratch.sh`, `test-stack-up.sh`, `doctor.sh` and `harvest-learnings.sh` now name `--only-bin`. Fixture `tests/wave2/test-sync-only-bin.sh` (40 checks, single- and two-tree): one `git status` change, hooks and skills untouched, while a full `--upgrade-bin` sync of the same project installs other scripts and the hooks. — field run, issue #390 - new(bug-lookup): **`bin/bug-lookup.sh` ranks the ledger against a whole pasted error message.** One word (a CE code, a BUG id, a keyword) keeps the full-entry path unchanged; several words print the top 3 entries, each with header, score and one excerpt line, and `--full N` prints the N-th in full. Scoring is one POSIX awk pass: distinct query terms weighted by IDF, CE codes and BUG ids x3, header terms x1.5, an exact BUG id +100. A "no confident match" banner is printed below 40% term-weight coverage or for a CE code no entry mentions. `--exact` keeps the old literal phrase match for a quoted multi-word keyword. Measured on 40 labelled dev queries (the set the weights were tuned on): top-3 9% before (the literal match finds almost nothing in a pasted message) vs 94% now, top-1 89%, confident-wrong 8%. On 18 held-out queries written before the ranker ran on them: top-1 92%, top-3 100%, confident-wrong 17%. All 3 wrong answers are no-match rows without a CE code: the 40% coverage floor caught none of the 4 such rows across both sets, and coverage does not separate them from correct answers. Only the unknown-CE rule makes "no match" work today. The fixture skips, with a note, any no-match row whose CE code has since entered the ledger. About 45 ms per query vs about 950 ms (mawk; about 300 ms on BWK awk); at most 179 words printed. The banner also fires on 3 correct dev answers. Identical output under gawk, mawk and BWK awk 20121220/20180827/20231127; macOS's 20200816 build itself is not tested. All arguments are joined (`"$*"`; it read only `$1` before), so an unquoted multi-word query is ranked too. The banner exits 0 (it still lists the nearest 3); 1 is no match. The script header no longer caches the ledger's size. The routing row and CLAUDE.md mention the pasted message (+9 words on the mdl and gate stubs), and the `mxcli-bugs` row drops its stale "32k words". Fixture `tests/wave2/test-bug-lookup.sh` (floors on both sets; the PASS count depends on the awks installed; 9 checks fail on master). Field evidence: the 2026-10-15 golden-set run. — maintainer request, MendixMau - chore(migration skills): **duplicated migration and build-loop rules become pointers to their single source** (CLAUDE.md: "single source of the rule; every other mention defers here"). `migrate-general.md` §9 (nine OS 11 → Mendix mapping tables, about 115 lines) becomes a pointer to `source-os11.md` § Concept Mapping, after its lines not found there (the Data/Client Action implication, the sequence-from-10000 rationale, the Permission row, "Mendix does not enforce the layering", the ODC reasons) move into `source-os11.md`; its own OS 11 scope note goes with it. `migrate-general.md` §4 NPE lifecycle points at `learned-microflow-patterns.md` § NPE as Form Backing Object (the NPE-associations paragraph, found only here, stays). `migration-pipeline.md`'s restated extractor loop and validator checklist point at `extractor-quality-loop.md` § The Loop. The five "BUG-02 fixed in v0.13.0" notes in `iterative-build-loop.md`, `migrate-general.md` and `migration-pipeline.md` become `bin/bug-lookup.sh BUG-02` pointers (the struck-through handoff row in `migrate-general.md` §7 is deleted as history; the `iterative-build-loop.md` note stays because `test-bug09-13.sh` T6 keeps it on purpose); `migrate-outsystems.md` stops calling `migrate-general.md` the concept mapping. Same commit, to keep the companion PR under the 10-file cap: `iterative-build-loop.md`'s step-13 navigation and "every displayed field" bullets point at `module-review.md` § 4c, its wireframe drift-sync paragraph at `conversion-runbook.md` § 3b, and `checkpoint-cutover.md`'s What's Next at the runbook's Stage 7 row. References to the removed text are re-pointed: the audit-field standard (`migrate-outsystems.md`) and module layering (`migration-pipeline.md`) at `source-os11.md`, and `source-triage.md`'s "Phase 2's quality checklist" at `extractor-quality-loop.md` Validator Requirements; `source-os11.md`'s DAO and UI tables gain the remaining §9 details. Only always-loaded change: that `source-triage.md` line (−2 words); baseline 73,522 → 73,520. — authoring audit, maintainer request diff --git a/project-bin/_common.sh b/project-bin/_common.sh index dfa4558c..879096d4 100755 --- a/project-bin/_common.sh +++ b/project-bin/_common.sh @@ -932,6 +932,222 @@ EOF_ARGS return 1 } +# --------------------------------------------------------------------------- +# The shared lock: mxtk_lock_take / mxtk_lock_release / mxtk_lock_live (+ mxtk_lock_fields) +# +# One implementation for exec.sh (the per-project .mpr-snapshots/.exec.lock) and test-scratch.sh +# (the same file, plus the machine-wide test-port lock). Bash 3.2, POSIX tools, no flock (macOS and +# Git Bash ship none). It replaces two "keep in step" copies (exec_lock_* / take_over). +# +# The lock file holds one line, "