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(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)
Expand Down
7 changes: 5 additions & 2 deletions bin/harvest-learnings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 —
Expand Down
22 changes: 22 additions & 0 deletions tests/wave2/fixtures/harvest-learnings/CAPTURE.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
61 changes: 61 additions & 0 deletions tests/wave2/test-harvest-learnings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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-<n>: ' 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
Expand Down
Loading