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 @@ -16,6 +16,7 @@ three commits past it), and a bug report can name a release instead of a sha nob
Sections dated before 2026-09-19 predate the cycle and stay as they are.

## Unreleased
- fix(bin/wire-company-brain.sh): **a heredoc built inside `$(cat <<EOF ...)` does not parse under bash 3.2** — the `$(...)` scanner reads the heredoc body as shell text and an apostrophe in it ("company's") opens a quote it never closes, so `doctor.sh` reported the script unparseable on macOS's stock bash while bash 5 here parsed it fine. Rebuilt with `IFS= read -r -d '' block <<EOF` (no heredoc inside `$(...)`), byte-identical output verified. The same trap was live in two more places, `claude-hooks/hooks/context-ceiling.sh` and `context-watch.sh` (a heredoc feeding python's stdin inside `$(...)`), fixed the same way with output re-verified against a synthetic transcript. Added a `bin/check-portability.sh` check for the pattern (excludes `<<<` here-strings and `$((...))` arithmetic shift, which false-triggered the raw grep) — workshop field report, macOS bash 3.2
- learn(bug-logs): **7 new drafts, 3 addenda and one feature-ask note from a module-rename field run, each re-checked on v0.24.0 where a probe was cheap.** New: `rename module` leaves XPath naming the old module; `rename page` renames a same-named folder instead (reproduced; root cause is a name match with no type check); a case-only `move` leaves an empty folder twin; a quoted DataGrid 2 association path passes `check` and fails CE1613; `check --references` ignores an in-script `rename module`; `diff-local` misses new (untracked) units; and `diff-local` prints a UUID for the module. Addenda: BUG-62 (no `rename snippet`), BUG-63 (`microflow_type` case, SQL-vs-Starlark entity types, `activity_count` scope; title widened), BUG-102 (legacy grid XPath has no offline route). Paste-ready upstream drafts for four, not filed. — field report from a colleague's naming-conventions project (mxcli v0.23.0, Mendix 11.12.4)
- learn(skills/learned-mdl-preflight.md): **STOP row 26: DataGrid 2 column and `sort by` paths over an association.** Write the column path unquoted, and do not sort over an association on ≤ v0.24.0. Both wrong forms pass `check --references` and fail CE1613 at build. — field report from a colleague's naming-conventions project (mxcli v0.23.0, Mendix 11.12.4)
- learn(skills/lint-that-actually-runs.md): **three more lint vocabulary facts, verified on v0.24.0, with a before/after rule.** `microflow_type` is `MICROFLOW`/`NANOFLOW`/`RULE`, and `microflows()` yields all three. SQL `PERSISTENT` is Starlark `Persistent`. `activity_count` counts top-level objects (splits, loops, annotations), not actions, and `activities_for()` does not see loop bodies. — field report from a colleague's naming-conventions project (mxcli v0.23.0, Mendix 11.12.4)
Expand Down
16 changes: 16 additions & 0 deletions bin/check-portability.sh
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,22 @@ while IFS= read -r f; do
'Use cd "$(dirname "$x")" && pwd.' ;;
esac

# bash 3.2's $(...) scanner does not recognise a heredoc opened inside it: it scans
# the body as shell text while hunting for the matching ')', so a stray apostrophe or
# quote in the body breaks the parse on macOS's default bash — invisible to bash -n on
# any machine that only has bash 5 (real incident: bin/wire-company-brain.sh, a
# "company's" in the block text). Strip the two lookalikes first: a `<<<` here-string
# (no heredoc body, just overlaps `<<`) and `$((...))` arithmetic (its `<<` is a
# bit-shift) — both false-triggered the raw pattern in testing.
case "$txt" in
*'$('*'<<'*)
_pc="$(printf '%s' "$txt" | sed -e 's/<<<//g' -e 's/\$(([^)]*))//g')"
if printf '%s' "$_pc" | grep -Eq '\$\([^)]*<<[^<]'; then
report "$f" "$ln" "heredoc opened inside \$(...) — unparseable on bash 3.2" \
"Read the heredoc into a variable first (IFS= read -r -d '' var <<EOF ... EOF), then use \"\$var\" inside \$(...)."
fi ;;
esac

case "$txt" in
*'mapfile '*|*'readarray '*)
report "$f" "$ln" "mapfile/readarray need bash 4 (macOS ships 3.2)" \
Expand Down
9 changes: 7 additions & 2 deletions bin/wire-company-brain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ $extra"
done

BEGIN='<!-- COMPANY-BRAIN:BEGIN -->'; END='<!-- COMPANY-BRAIN:END -->'
block="$(cat <<BLK
# bash 3.2's $(...) scanner does not recognise a heredoc opened inside a command
# substitution: it scans the body as shell text, so an apostrophe in the body (e.g.
# "company's") opens a quote that is never closed and the parse fails with "unexpected
# EOF while looking for matching `''" on macOS's default bash. Build the block with a
# heredoc feeding `read -d ''` instead, which every caller supports the same way.
IFS= read -r -d '' block <<BLK || true
$BEGIN
## Company brain

Expand All @@ -53,7 +58,7 @@ integration, and before choosing a component, read \`$BRAIN/ROUTING.md\`** and f
that fires. It loads on demand; nothing from it is copied here.
$END
BLK
)"
block="${block%$'\n'}"
written=0
while IFS= read -r CL; do
[ -n "$CL" ] || continue
Expand Down
8 changes: 6 additions & 2 deletions claude-hooks/hooks/context-ceiling.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ if [ -f "$cache" ]; then
fi

if [ -z "$ctx" ]; then
ctx=$("$PY" - "$transcript" <<'PY' 2>/dev/null
# bash 3.2 does not recognise a heredoc opened inside $(...): it scans the body as
# shell text while hunting for the matching ')', so a stray quote in the body can
# break the parse on macOS's default bash (same trap as bin/wire-company-brain.sh).
# Read the heredoc into a variable first, then pipe it in — no heredoc inside $(...).
IFS= read -r -d '' _ctx_py <<'PY' || true
import json, os, sys
path = sys.argv[1]; CAP = 8 << 20
def newest(chunk):
Expand Down Expand Up @@ -116,7 +120,7 @@ try:
else: print(0)
except Exception: print(0)
PY
)
ctx=$(printf '%s' "$_ctx_py" | "$PY" - "$transcript" 2>/dev/null)
case "$ctx" in ''|*[!0-9]*) ctx=0 ;; esac
printf '%s %s' "$now" "$ctx" > "$cache" 2>/dev/null
fi
Expand Down
8 changes: 6 additions & 2 deletions claude-hooks/hooks/context-watch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ printf '%s' "$now" > "$throttle" 2>/dev/null
# here can be 600KB (an inlined screenshot), so a fixed byte tail routinely
# slices mid-line and finds no parseable record. Stops at the first usage
# record found, or at the cap.
ctx=$("$PY" - "$transcript" <<'PY' 2>/dev/null
# bash 3.2 does not recognise a heredoc opened inside $(...): it scans the body as
# shell text while hunting for the matching ')', so a stray quote in the body can
# break the parse on macOS's default bash (same trap as bin/wire-company-brain.sh).
# Read the heredoc into a variable first, then pipe it in — no heredoc inside $(...).
IFS= read -r -d '' _ctx_py <<'PY' || true
import json, os, sys

path = sys.argv[1]
Expand Down Expand Up @@ -111,7 +115,7 @@ try:
except Exception:
print(0)
PY
)
ctx=$(printf '%s' "$_ctx_py" | "$PY" - "$transcript" 2>/dev/null)

case "$ctx" in ''|*[!0-9]*) exit 0 ;; esac
[ "$ctx" -gt 0 ] || exit 0
Expand Down
Loading