diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab32d97..1cd9cac9 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(harvest-learnings): **section 1 drafted only the first `## ` section of each bug-log file.** The awk splitter's `print buf "\x01"` put a newline after each delimiter, so every later section began with an empty line, read an empty heading and was skipped. It is `printf "%s\x01", buf` now. Field run on a Mendix 10.24 app with mxcli v0.27.0 (43 bug-log files, 54 `## ` sections): `harvest-learnings.sh` at 393d253 drafted 43, the fix drafts 54. Pinned by `tests/wave2/test-harvest-learnings.sh` T5, on a two-file fixture that keeps a captured log's structure with its text replaced (fixed: 7 of 7 sections, 20 of 20 checks; 393d253: 2 of 7, T5 fails). The splitter's delimiter bytes were checked under macOS awk 20200816 only. — saingge - fix(script-owners): **the restatement guard now parses under macOS's default bash 3.2, so `exec.sh` no longer skips it silently** (guard added in #498). `exec.sh` runs `bash script-owners.sh check … 2>/dev/null || true`; under /bin/bash 3.2.57 the file failed to parse (an apostrophe in a comment inside `$(...)`, then `case` patterns without a leading `(`), so the call (and the ledger `record` call) printed nothing and warned nothing. Fixture `test-restatement-guard.sh` under /bin/bash 3.2.57: 16 of 58 pass before, 58 of 58 after; `bin/check-scripts.sh` now also parses every script with /bin/bash when the bash on PATH is a different one. — saingge - fix(facts-lock): **`check` no longer reports "N BRD(s) agree with all 0 frozen fact(s)" when nothing was compared** (#519). A corpus whose BRDs carry identifiers only under keys the harvest does not read (`domainEntities[].name`, `pages[].name`) froze an empty lock, and `check` printed what reads as a pass. With 0 frozen facts, `check` now prints `PENDING facts-lock: 0 identifiers found under any known key in N BRD(s): the lock is empty and check proves nothing (keys read: ...)`, or, when identifiers exist but no lock was built, `PENDING ... run build first; check compared nothing`; `build` prints the same empty-lock line when it froze 0 facts and found no conflicts. Exit codes are unchanged (an exit-3 "nothing measured" and harvesting `name` keys are left for a separate change). Fixture: `test-facts-lock.sh` (the issue's repro: 3 fail on master; 19/19). — AlvinnYeo (#519) - fix(journey-runner): **the RUNG 1 landing guard now waits up to its 12 s for `ui.ready`** (#536). It called `locator.isVisible({ timeout: 12000 })`, and Playwright ignores that timeout and answers at once, so a page that rendered more than 300 ms after the per-action settle (a progress bar, a microflow before the page opens, a step ending in a fixed `settleMs`) read `reached: FAIL` and every later rung of the step INVALID. It now uses `waitFor({ state: 'visible', timeout: 12000 })`, and the NOT-REACHED screenshot is taken after that wait. Only the landing guard changes; the same no-wait call in `act()` (`click`, lines 184/210/222), the named-control probe and `helpers.js`/`monkey.js`/`full-app-walkthrough.js` are left for a separate change. **Behaviour change to check in your project:** a journey whose ready selector never appears now spends 12 s per such step before it reports NOT-REACHED. Verified by inspection and a Playwright probe (hidden element shown after 2 s: `isVisible` false in 74 ms, `waitFor` true in 2.3 s); no fixture drives the runner's step loop. — AlvinnYeo (#536) diff --git a/bin/harvest-learnings.sh b/bin/harvest-learnings.sh index 0e3a77a1..d54c4c24 100755 --- a/bin/harvest-learnings.sh +++ b/bin/harvest-learnings.sh @@ -73,10 +73,13 @@ if [ -d "$PROJECT_DIR/bug-logs" ]; then find "$PROJECT_DIR/bug-logs" -name '*.md' -type f | sort | while IFS= read -r f; do # Split the file on '## ' headings; test each heading's distinctive text against the # toolkit log. awk emits candidate sections whole, delimited for the outer loop. + # printf, not print: print appends a newline AFTER the \x01, so every section but the + # first began with an empty line, its heading read as empty, and it was skipped, so each + # bug-log file drafted one entry. awk ' - /^## / { if (buf != "") print buf "\x01"; buf = "" ; head = 1 } + /^## / { if (buf != "") printf "%s\x01", buf; buf = "" ; head = 1 } { if (head) buf = buf $0 "\n" } - END { if (buf != "") print buf "\x01" } + END { if (buf != "") printf "%s\x01", buf } ' "$f" | while IFS= read -r -d $'\x01' section; do heading="$(printf '%s' "$section" | head -1 | sed 's/^## *//; s/[~*`]//g')" # Distinctive slice: strip a leading BUG-nnn tag (numbering diverges across repos — diff --git a/tests/wave2/fixtures/harvest-learnings/CAPTURE.md b/tests/wave2/fixtures/harvest-learnings/CAPTURE.md new file mode 100644 index 00000000..22bf7e49 --- /dev/null +++ b/tests/wave2/fixtures/harvest-learnings/CAPTURE.md @@ -0,0 +1,22 @@ +# harvest-learnings — what these two bug logs are, and what was changed + +`harvest-learnings.sh` section 1 splits each `bug-logs/*.md` file on its `## ` headings, so per +`CLAUDE.md` -> "Shipping an instrument" rule 1 its fixture input reproduces a real bug log. These +two files reproduce the **structure** of the bug logs that exposed the first-section-only defect +(a Mendix 10.24 project, 2026-10-13: 12 sections in one file and 1 in a second; the harvester +drafted the first section of each). **Only the text was changed**, because the logs are a client +project's, and the section counts were cut to 5 and 2. + +**What is verbatim, because the splitter sees exactly these:** + +- a `# ` title, an intro paragraph and a bullet list (with indented continuation lines) before + the first `## `, which belong to no section; +- one blank line between sections, and a section that is not a bug at all + (`## Improvement ideas, …`), which the heuristic still drafts; +- `> ` status blocks with `> - ` bullets, `**Label:**` lines, and a fenced block inside a section; +- `BUG-DRAFT-`, `NOTE-` and `TOOLKIT-DRAFT-` heading prefixes with a trailing `(date)`; +- the last section running to the end of the file, which ends in one newline. + +`test-harvest-learnings.sh` T5 appends one section whose heading the toolkit log already carries, +taken from the clone's own `bug-logs/mxcli-bugs.md` at run time, so the known-heading filter is +checked on a later section, the path the defect broke. diff --git a/tests/wave2/fixtures/harvest-learnings/bug-logs/2026-10-13-field-log.md b/tests/wave2/fixtures/harvest-learnings/bug-logs/2026-10-13-field-log.md new file mode 100644 index 00000000..f6de270d --- /dev/null +++ b/tests/wave2/fixtures/harvest-learnings/bug-logs/2026-10-13-field-log.md @@ -0,0 +1,52 @@ +# mxcli and toolkit notes from a feature rebuild, 2026-10-13 + +Project-local bug log in the toolkit ledger format, so `bin/harvest-learnings.sh` can draft +toolkit inbox entries from it. Environment: mxcli v0.26.0, Mendix 10.24, macOS, mxbuild +through the project's Docker shim. + +- **Destinations:** a `BUG-DRAFT` goes to mxcli as an issue, and a `TOOLKIT-DRAFT` goes to the + toolkit as a PR or an issue. +- **Status:** nothing here has been filed. +- **Names:** strip project names before anything goes to a public repo. +- This file is excluded from the project repo. + +## Improvement ideas, most time saved first + +1. One check that names the failing statement before the exec, not after it. +2. A refusal that says which statement blocked it and why. + +## BUG-DRAFT-alpha-quoted-path: a quoted path segment is stored with its quotes in one layout (2026-10-13) + +> **Status:** reproduced on a throwaway copy. +> - The script passed `check`. +> - The build failed with one expression error. + +**mxcli version:** v0.26.0. **Severity:** Medium. + +**Repro:** + +``` +change $Item ( + "A" = $Other/"A" +); +``` + +**Workaround:** write the segment unquoted. + +## BUG-DRAFT-beta-loop-edit: a change inside a loop body is refused (2026-10-13) + +> **Status:** seen once. The refusal text was not kept. + +**Actual:** +- The refusal names the loop. +- Nothing is written. + +## NOTE-gamma-rule-count: a lint rule counts actions but not decisions (2026-10-13) + +The count includes actions in branches and loop bodies. Decisions, loops and returns are not +counted. + +## TOOLKIT-DRAFT-delta-brief-line: delegation briefs should carry one more line (2026-10-13) + +- Every worker brief names the lookup to run before experimenting. +- **Why:** two workers spent a turn each on a known quirk. diff --git a/tests/wave2/fixtures/harvest-learnings/bug-logs/2026-10-13-second-log.md b/tests/wave2/fixtures/harvest-learnings/bug-logs/2026-10-13-second-log.md new file mode 100644 index 00000000..5b1c35c7 --- /dev/null +++ b/tests/wave2/fixtures/harvest-learnings/bug-logs/2026-10-13-second-log.md @@ -0,0 +1,9 @@ +# Toolkit notes, 2026-10-13 + +## TOOLKIT-DRAFT-epsilon-first-of-two: the first section of a second file (2026-10-13) + +> **Status:** written to pin the per-file behaviour. + +## TOOLKIT-DRAFT-zeta-second-of-two: the second section of a second file (2026-10-13) + +**Workaround:** none needed. diff --git a/tests/wave2/test-harvest-learnings.sh b/tests/wave2/test-harvest-learnings.sh index df560b24..4752af14 100755 --- a/tests/wave2/test-harvest-learnings.sh +++ b/tests/wave2/test-harvest-learnings.sh @@ -22,6 +22,9 @@ # invoked script's own location (dirname "$0"/..), so copying just bin/ + project-bin/ into # a git-free directory and invoking the copy from there IS a toolkit worktree with no # history — no env override needed. +# T5 section 1 drafts every '## ' section of every bug-log file, not only the first of each, +# and still skips a later section whose heading the toolkit log carries. Fixture: +# fixtures/harvest-learnings/ (see its CAPTURE.md). # # Usage: bash tests/wave2/test-harvest-learnings.sh /path/to/harvest-learnings.sh @@ -174,6 +177,64 @@ else fi fi +# --------------------------------------------------------------------------------------------- +# T5: section 1 drafts every section of a multi-section bug log, not only the first. awk's print +# put a newline after each \x01 delimiter, so sections 2..n began with an empty line, read an +# empty heading and were skipped: each bug-log file drafted one entry. +# --------------------------------------------------------------------------------------------- +echo "== T5: every section of a multi-section bug log is drafted, not only the first ==" + +FIX5="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/fixtures/harvest-learnings" +if [ ! -f "$CLONE/bin/harvest-learnings.sh" ] || [ ! -f "$CLONE/bug-logs/mxcli-bugs.md" ]; then + bad "control: no isolated clone with bug-logs/mxcli-bugs.md (see T1) — fixture cannot run" +elif [ ! -d "$FIX5/bug-logs" ]; then + bad "control: fixture missing at $FIX5/bug-logs" +else + P5="$WORK/multiproj"; mkdir -p "$P5" + cp -R "$FIX5/bug-logs" "$P5/bug-logs" + # Every fixture heading, stripped the way the harvester strips it. + EXPECTED5="$(cat "$FIX5"/bug-logs/*.md | grep '^## ' | sed 's/^## *//; s/[~*`]//g')" + N5="$(printf '%s\n' "$EXPECTED5" | grep -c .)" + # A later section whose heading the toolkit log already carries must still be filtered out. + # Taken with no ~ * or ` in it: the harvester strips those from a heading before matching it + # against the log, so a heading that has them never matches and is drafted every time. + KNOWN5="$(grep -m1 -E '^## BUG-[0-9]+: [^~*`]+$' "$CLONE/bug-logs/mxcli-bugs.md")" + if [ -n "$KNOWN5" ]; then + printf '\n%s\n\nA section the toolkit log already carries; never drafted.\n' "$KNOWN5" \ + >> "$P5/bug-logs/2026-10-13-second-log.md" + else + bad "control: the clone's bug-logs/mxcli-bugs.md has no plain '## BUG-: ' heading to reuse" + fi + + OUT5="$(bash "$CLONE/bin/harvest-learnings.sh" "$P5" --to "$WORK/inbox5" 2>&1)"; RC5=$? + DRAFT5="$WORK/inbox5/$STAMP-multiproj-bugs.md" + [ "$RC5" -eq 0 ] && ok "exit 0" || bad "exit $RC5, expected 0" "$OUT5" + if [ ! -f "$DRAFT5" ]; then + bad "no bugs draft written at $DRAFT5" "$OUT5" + else + GOT5="$(grep -c '^## \[candidate' "$DRAFT5")" + if [ "$GOT5" -eq "$N5" ]; then + ok "$N5 of $N5 fixture sections drafted, across 2 files" + else + bad "$GOT5 sections drafted, expected $N5" "$(grep '^## \[candidate' "$DRAFT5")" + fi + MISSING5="" + while IFS= read -r h; do + grep -qF "] $h" "$DRAFT5" || MISSING5="$MISSING5$h"$'\n' + done <<< "$EXPECTED5" + if [ -z "$MISSING5" ]; then + ok "every heading drafted by name, the later sections of each file included" + else + bad "headings not drafted" "${MISSING5%$'\n'}" + fi + if [ -n "$KNOWN5" ] && grep -qF "${KNOWN5#'## '}" "$DRAFT5"; then + bad "a heading the toolkit log carries was drafted (the known-heading filter broke)" + else + ok "the known heading, added as a later section, is not drafted" + fi + fi +fi + echo "" echo "PASS=$PASS FAIL=$FAIL ($WORK)" # Every draft this fixture produced lives under $WORK (the clone's contrib/inbox, or the no-git