From 0c4dc18a3312a7734887b5dac7ad0b2b892d91bc Mon Sep 17 00:00:00 2001 From: saingge <262679532+saingge@users.noreply.github.com> Date: Wed, 21 Oct 2026 16:16:24 +0200 Subject: [PATCH] fix(harvest-learnings): draft every '## ' section of a bug log, not only the first The awk splitter ended each section with print buf "\x01", and print appends a newline after the delimiter. Every section after the first therefore began with an empty line, read an empty heading and was skipped, so each bug-log file drafted one entry. It now uses printf "%s\x01", buf. Test: tests/wave2/test-harvest-learnings.sh T5 runs on a two-file fixture (5 and 2 sections) that keeps the structure of a captured field log, with its text replaced (see the fixture's CAPTURE.md). T5 expects all 7 sections to be drafted and a heading the toolkit log already carries to be filtered out. Run against origin/master 393d253: with master's bin/harvest-learnings.sh the fixture passes 18 of 20 checks and T5 fails (2 of 7 sections drafted); with the fixed script it passes 20 of 20. Field run, a Mendix 10.24 app with mxcli v0.27.0 (43 bug-log files, 54 '## ' sections): the script at 393d253 drafts 43, the fix drafts 54. The splitter's delimiter bytes were checked under macOS awk 20200816 only. --- CHANGELOG.md | 1 + bin/harvest-learnings.sh | 7 ++- .../fixtures/harvest-learnings/CAPTURE.md | 22 +++++++ .../bug-logs/2026-10-13-field-log.md | 52 ++++++++++++++++ .../bug-logs/2026-10-13-second-log.md | 9 +++ tests/wave2/test-harvest-learnings.sh | 61 +++++++++++++++++++ 6 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 tests/wave2/fixtures/harvest-learnings/CAPTURE.md create mode 100644 tests/wave2/fixtures/harvest-learnings/bug-logs/2026-10-13-field-log.md create mode 100644 tests/wave2/fixtures/harvest-learnings/bug-logs/2026-10-13-second-log.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ee82ab78..da204347 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(progress-report, build-plan-status): **HARNESS rows are no longer counted as built rows, and a module's `verify-module.sh` row shows once, as that module's sign-off status on its phase card, instead of as a row.** A HARNESS step is a module's sign-off, not a build, so it is out of every "x of y built" count: the phase cards, the plan rollup and the Stage 5 card in `progress-report.html`, and the lede of `build-plan-status.sh --html` ("N build rows", plus a "Not counted: N HARNESS rows" note). The verify row's evidence is read exactly as #501 reads it (`harness_verify`'s rule, now `verify_evidence` + `module_status` in `progress-report.py`; nothing new measured, no extra script run, no mxcli or mxbuild call) and shown as one line per module: `Verified ✓ (LOOK n/n)`, `Verify owed - LOOK + CONFIRM`, `Verify stale (N builds since)` (or `(a script was marked done- after it)`), `Verify not run`, `built? no verify summary`, `Verify not clean ()` for a summary with a FAULT, FINDING or SKIPPED row, or `Verify clean , LOOK n/n - held: State says ` when State is blocked or pending a person (held is not signed off, as #501 never promoted it); a State cell that disagrees is flagged under it, the rollup says how many modules are verified, and the Stage 5 card, KPI and owed text add ", k of n modules signed off". The Phase state badge is read from the build rows only (a phase of HARNESS rows alone says "sign-off only" and its card "Sign-off phase: no build rows"). A module with no verify row shows no status. Other HARNESS rows (a coherence pass, a harness run) stay in the row list, marked "(not counted)". `build-plan.md`, `build-plan.json` and the text output are unchanged, so agents still see every HARNESS step. **Behaviour change to check in your project:** the progress report's built counts drop the HARNESS rows (a plan of 10 rows with 2 HARNESS reads "x of 8"), and the verify rows leave the row tables. Fixture: `test-progress-harness-verify.sh` (H1-H10, N1-N6, both layouts). — MendixMau (decision 2026-10-21) - fix(exec): **a script restated from a stale DESCRIBE now WARNs instead of silently reverting an earlier exec** (#261). A script drafted from a DESCRIBE taken before an earlier script of the batch ran put four old messages back, with check, mxbuild and lint green. New `project-bin/describe-baseline.sh stamp` writes `-- baseline: sha256=` (plus `-- baseline-mxcli: `) from a DESCRIBE at draft time; `exec.sh` runs `verify` only for a script that carries the header, so other scripts cost one grep and no mxcli call. It reports DRIFT with the first lines of the diff, STALEHEADER, and RESTAMP after an mxcli upgrade (DESCRIBE text may change, so that is not called drift). A re-run by the script's own last recorded write (the #269 ledger) and a fresh model are not drift. Nothing is refused and the exit codes do not change. Preflight STOP 21 gains one clause (`-- supersedes:`, `stamp`, exec.sh only warns) and its trigger names `create or modify` (+24 words). Not-found DESCRIBE and `--version` output are captured from mxcli v0.26.0. Lands after the #269 PR; with it, supersedes draft #311. **Behaviour change to check in your project:** `exec.sh` output can carry `WARN: stale draft: …` lines, and the BUILD-LOG detail cell `WARN stale baseline: `; `MXTK_RESTATE_GUARD=off` skips the check. — AlvinnYeo (#261) - fix(exec): **re-running an older `mdlsource/` script over a newer owner's live work now WARNs instead of passing silently** (#269). Three field apps had 99/579, 130/357 and 38/159 documents created by more than one script, and a replay of an older one reverted the newer with exit 0 and every gate green. New `project-bin/script-owners.sh` maps each microflow, nanoflow, page and snippet to the scripts that create or restate it, in run order (digit runs compared as numbers, so `2-core/` runs before `11-fixes/`; `done-` ignored), and reads `-- supersedes:` / `-- superseded-by:` headers. `exec.sh` records every kept write in a machine-local last-writer ledger (`.claude/loop/script-owners/`, self-gitignored, compacted per write); `restore-mpr.sh` marks it on a manual restore. A replay WARNs only when the newer owner's write is live: ledger first, else one DESCRIBE per document, at most 5 per exec (the rest print `CONFLICT (not checked)`). A new owner without a supersedes header gets one `NOTE:` line on its first run, kept out of BUILD-LOG. Nothing is refused and the exit codes do not change. With no co-owner, `exec.sh` runs one grep over `mdlsource/` and no mxcli call. `gate-check.sh` prints the co-owned count when it is above 0. Part of the rebuilt draft #311 (with the #261 PR). **Behaviour change to check in your project:** `exec.sh` output can carry `WARN: restatement: …` and `NOTE: new owner of …` lines; the BUILD-LOG detail cell can carry `WARN reverts: `. Set `MXTK_RESTATE_GUARD=off` to skip the check. — MendixMau (#269) 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