Skip to content
Closed
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(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 ✓ <date> (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 <date> (<why>)` for a summary with a FAULT, FINDING or SKIPPED row, or `Verify clean <date>, LOOK n/n - held: State says <State>` 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: <Doc> sha256=<hash>` (plus `-- baseline-mxcli: <version>`) 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: <docs>`; `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: <docs>`. Set `MXTK_RESTATE_GUARD=off` to skip the check. — MendixMau (#269)
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